Recursion

In this part we will discuss the concept of a call stack and recursion. Recursion is when a method calls itself (directly or indirectly). Recursion is specially well suited for implementing computation that are itself recursively defined (as we will see below by means of the example of Fibonacci numbers).

Tail recursion

Tail recursion is when the recursive call (the place inside the method body where the method calls itself) is the last statement in the method body.

As a simple example of tail recursion consider printing all integers up to n in decreasing order.

In [9]:
package lecture;

public class UpToNReverse {
    
    public static void printUpToN (int n) {
        System.out.println("" + n);
        if (n > 1)
            printUpToN(n-1);
    }
}
Out[9]:
lecture.UpToNReverse
In [6]:
package lecture;

public class UpToN {
    
    public static void printUpToN (int cur, int n) {
        System.out.println("" + cur);
        if (cur < n)
            printUpToN(cur+1, n);
    }
}
Out[6]:
lecture.UpToN
In [7]:
import lecture.UpToN;

UpToN.printUpToN(1,6);
1
2
3
4
5
6
Out[7]:
null
In [10]:
import lecture.UpToNReverse;

UpToNReverse.printUpToN(6);
6
5
4
3
2
1
Out[10]:
null

What if want to print the numbers in increasing order instead. Can recursion still be used? Turns out yes, the only thing that we have to change is to place the recursive call before the print statement.

In [1]:
package lecture;

public class UpToN {
    
    public static void printUpToN (int n) {
        System.out.println("starting call for " + n);
        if (n > 1)
            printUpToN(n-1);
        System.out.println("finished recursion for " + n);
        System.out.println("" + n);
    }
}
Out[1]:
lecture.UpToN
In [2]:
import lecture.UpToN;

UpToN.printUpToN(6);
starting call for 6
starting call for 5
starting call for 4
starting call for 3
starting call for 2
starting call for 1
finished recursion for 1
1
finished recursion for 2
2
finished recursion for 3
3
finished recursion for 4
4
finished recursion for 5
5
finished recursion for 6
6
Out[2]:
null

As another example of tail recursion let's consider searching through a singly linked list.

In [13]:
package lecture;

public class StringList {
    
    protected class StringListElement {
        public String data;
        public StringListElement next;
        
        public StringListElement(String data) {
            this.data = data;
            this.next = null;
        }
    }
    
    private StringListElement head;
    
    public StringList() {
        this.head = null;
    }
    
    public StringList(String ... e) {
        this.head = null;
        for(String el: e) {
            add(el);
        }
    }
    
    public void add(String e) {
        StringListElement n = new StringListElement(e);
        if (head == null) {
            head = n;
        }
        else {
            StringListElement last = findLast(head);
            last.next = n;
        }
    }
    
    public String get(int position) throws Exception {
        StringListElement cur = head;
        
        for(int i = 0; i < position; i++) {
            if (cur == null) {
                throw new Exception(String.format("no element at position %d", position));
            }
            cur = cur.next;
        }
        return cur.data;
    }
    
    // really not a smart way to implement this, but illustrates recursion
    public StringListElement findLast(StringListElement e) {
        if (e.next == null)
            return e;
        else
            return findLast(e.next);
    }
    
    public String toString() {
        StringListElement cur = head;
        StringBuilder b = new StringBuilder();
        b.append("[");
        while(cur != null) {
            b.append(cur.data);
            if (cur.next != null)
                b.append(", ");
            cur = cur.next;
        }
        b.append("]");
        return b.toString();
    }
}
Out[13]:
lecture.StringList
In [14]:
import lecture.StringList;

StringList x = new StringList("Peter", "Bob", "Alice");
x.add("Jane");
return ((StringList) x).toString();
Out[14]:
[Peter, Bob, Alice, Jane]

Fibonacci Numbers

The Fibonacci numbers are recursively defined as: f(0) = 0, f(1) = 1, for n > 1: f(n) = f(n-1) + f(n-2)

In [15]:
package lecture;

public class Fibonacci {
    
    public static int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        else
            return fib(n - 1) + fib(n - 2);
    }
}
Out[15]:
lecture.Fibonacci
In [16]:
import lecture.Fibonacci;

return Fibonacci.fib(10);
Out[16]:
55

Now let's try to see what is the sequence of calls in this implementation.

In [14]:
package lecture;

import java.util.List;
import java.util.ArrayList;


public class Fibonacci {
    
    private static List<Integer> f = new ArrayList<Integer> ();

    public static int doFib(int n) {
        for(int i = 0; i < 20; i++) 
            f.add(-1);
        return fib(n);
    }
    public static int fib(int n) {
        if (f.get(n) != -1)
            return f.get(n);
        System.out.printf("fib(%d)\n", n);
        if (n == 0) {
            f.set(n, 0);
            return 0;
        }
        if (n == 1) {
            f.set(n, 1);
            return 1;
        }
        else {
            int res = fib(n - 1) + fib(n - 2);
            f.set(n, res);
            return res;
        }
    }
}
Out[14]:
lecture.Fibonacci
In [15]:
import lecture.Fibonacci;

return Fibonacci.doFib(5);
fib(5)
fib(4)
fib(3)
fib(2)
fib(1)
fib(0)
Out[15]:
5

Dynamic Programming

Problems like computing Fibonacci numbers where a solution is computed from multiple subsolutions allow for an optimization that called dynamic programming which enables repeated computed of partial solutions to be avoided by storing them. Thus, dynamic programming trades memory for computational efficiency.

In [27]:
package lecture;

import java.util.Vector;

public class FibonacciDP {
    
    public static Vector<Integer> f = new Vector<Integer>();
    
    public static int fib(int n) {
        System.out.printf("fib(%d) - ", n);
        if (f.size() > n) {
            System.out.print("use cached value\n");
            return f.get(n);
        }
        System.out.print("compute value\n");
        if (n == 0) {
            f.add(0);
            return 0;
        }
        if (n == 1) {
            f.add(1);
            return 1;
        }
        else {
            int fn = fib(n - 1) + fib(n - 2);
            f.add(fn);
            return fn;
        }
    }
}
Out[27]:
lecture.FibonacciDP
In [28]:
import lecture.FibonacciDP;

return FibonacciDP.fib(5);
fib(5) - compute value
fib(4) - compute value
fib(3) - compute value
fib(2) - compute value
fib(1) - compute value
fib(0) - use cached value
fib(1) - use cached value
fib(2) - use cached value
fib(3) - use cached value
Out[28]:
16

Tree data structures and Traversal

As an eample of a tree data structure, let's consider an organigram of a company which records who is reporting to whom. We can model this information as a tree, where the node corresponding to a person X is a child of the node corresponding to a person Y is X reports to Y. Once we have created a data structure to store such information and used it to instantiate an organigram, we may want to visualize it. One option for is to print one node in the tree per line and indendent node by a number of tabs that equals the depth of the element in the tree.

In [12]:
package lecture;

public class TreeNode {
    
    public String person;
    public TreeNode[] children;
    
    public TreeNode(String person, TreeNode ... children) {
        this.person = person;
        this.children = children;
    }
    
}
Out[12]:
lecture.TreeNode

Now how can we create a print all nodes in the tree with the right indentation. First, we observe that the problem of printing a tree like this can be broken down into subtasks:

  1. We print the root of the current subtree with the current indentation, say i
  2. Then we print the subtrees rooted at the children of the current node with indentation i+1.

This recursive description immeditialy translates into code:

In [11]:
package lecture;

public class TreePrinter {
    
    public static void printTree(TreeNode root) {
        printTree(root, 0);
    }
    
    public static void printTree(TreeNode t, int indent) {
        StringBuilder ind = new StringBuilder();
        for(int i = 0; i < indent; i++)
            ind.append('\t');
        System.out.printf("%s%s\n", ind, t.person);
        if (t.children != null)
            for(TreeNode child: t.children)
                printTree(child, indent + 1);
    }
    
}
Out[11]:
lecture.TreePrinter
In [13]:
import lecture.TreeNode;
import lecture.TreePrinter;

// in this example Bob reports to Peter who reports to Alice. Jane also reports to Alice.
// Alice
//     Peter
//         Bob
//     Jane
TreeNode root = new TreeNode("Alice", 
                             new TreeNode("Peter",
                                          new TreeNode("Bob", null)
                                         ),
                             new TreeNode("Jane", null)
                             );

TreePrinter.printTree(root);
Alice
	Peter
		Bob
	Jane
Out[13]:
null

To better highlight the call structure, consider an even simpler example: counting the number of nodes in the tree. Again we can define this number recursively. The number $C(x)$ of nodes in a tree rooted at a node $x$ is

$$1 + \sum_{c\,\,\text{is child of x}} C(c)$$
In [14]:
package lecture;

public class TreeCounter {
    
    
    public static int countNodes(TreeNode t) {
        int i = 1;
        if(t.children != null)
            for(TreeNode child: t.children)
                i += countNodes(child);
        return i;
    }
    
}
Out[14]:
lecture.TreeCounter
In [15]:
import lecture.TreeNode;
import lecture.TreeCounter;

// in this example Bob reports to Peter who reports to Alice. Jane also reports to Alice.
// Alice
//     Peter
//         Bob
//     Jane
TreeNode root = new TreeNode("Alice", 
                             new TreeNode("Peter",
                                          new TreeNode("Bob", null)
                                         ),
                             new TreeNode("Jane", null)
                             );

return TreeCounter.countNodes(root);
Out[15]:
4

Searching in Trees - Tree Traversal

Say we have a tree like the one above, but the tree is limited to be binary. That is, no node has more than two children. Every node stores some payload, for now a String. Assume we want to search for a node with a particular payload in the tree. We can again break this down recursively:

  1. Is the current node the one we are searching for? If yes, then return true
  2. Is the search for the node in the left subtree successfull? If yes, then return true
  3. Is the search for the node in the right subtree successfull? If yes, then return true
  4. Return false

Traversal Orders

Observe that the correctness of the algorithm above is not affected by the order in which we execute steps 1 to 3. Thus, we have 3 different ways how to traverse the tree in which nodes will be visited in different orders:

  • Pre-order: Visit the current node, then visit the left subtree, then the right subtree
  • Post-order: Visit the left subtree, visit the right subtree, visit the current node
  • In-order: Visit the left subtree, visit the current node, visit the right subtree
In [1]:
package lecture;

public class BinaryTreeNode {
    
    public String person;
    public BinaryTreeNode leftChild = null;
    public BinaryTreeNode rightChild = null;
    
    public BinaryTreeNode(String person, BinaryTreeNode leftChild, BinaryTreeNode rightChild) {
        this.person = person;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }
    
    public BinaryTreeNode (String person, BinaryTreeNode leftChild) {
        this(person, leftChild, null);
    }
    
    public BinaryTreeNode (String person) {
        this(person, null, null);
    }
}
Out[1]:
lecture.BinaryTreeNode
In [2]:
package lecture;

public class TreeTraversal {

    public static boolean PreOrder (BinaryTreeNode t, String key) {
        if (t == null)
            return false;
        System.out.printf("check self (%s)\n", t.person);
        if (t.person.equals(key)) {
            return true;
        }
        if (t.leftChild != null) {
            System.out.printf("visit left of (%s)\n", t.person);
            if (PreOrder(t.leftChild, key))
                return true;
        }
        if (t.rightChild != null) {
            System.out.printf("visit right of (%s)\n", t.person);
            if (PreOrder(t.rightChild, key)) 
                return true;
        }
        return false;
    }

    public static boolean PostOrder (BinaryTreeNode t, String key) {
        if (t == null)
            return false;
        if (t.leftChild != null) {
            System.out.printf("visit left of (%s)\n", t.person);
            if (PostOrder(t.leftChild, key))
                return true;
        }
        if (t.rightChild != null) {
            System.out.printf("visit right of (%s)\n", t.person);
            if (PostOrder(t.rightChild, key)) 
                return true;
        }
        System.out.printf("check self (%s)\n", t.person);
        if (t.person.equals(key)) {
            return true;
        }
        return false;
    }

    public static boolean InOrder (BinaryTreeNode t, String key) {
        if (t == null)
            return false;
        if (t.leftChild != null) {
            System.out.printf("visit left of (%s)\n", t.person);
            if (InOrder(t.leftChild, key))
                return true;
        }
        if (t.person.equals(key)) {
            System.out.printf("check self (%s)\n", t.person);
            return true;
        }
        if (t.rightChild != null) {
            System.out.printf("visit right of (%s)\n", t.person);
            if (InOrder(t.rightChild, key)) 
                return true;
        }
        return false;
    }
}
Out[2]:
lecture.TreeTraversal
In [3]:
import lecture.BinaryTreeNode;
import lecture.TreeTraversal;

// in this example Bob reports to Peter who reports to Alice. Jane also reports to Alice.
// Alice
//     Peter
//         Bob
//     Jane
BinaryTreeNode root = new BinaryTreeNode("Alice", 
                             new BinaryTreeNode("Peter",
                                          new BinaryTreeNode("Bob")
                                         ),
                             new BinaryTreeNode("Jane")
                             );

boolean hasJane;

System.out.println("********** PREORDER **********");
hasJane = TreeTraversal.PreOrder(root, "Jane");
System.out.println("********** POSTORDER **********");
hasJane = TreeTraversal.PostOrder(root, "Jane");
System.out.println("********** INORDER **********");
hasJane = TreeTraversal.InOrder(root, "Jane");

return hasJane;
********** PREORDER **********
check self (Alice)
visit left of (Alice)
check self (Peter)
visit left of (Peter)
check self (Bob)
visit right of (Alice)
check self (Jane)
********** POSTORDER **********
visit left of (Alice)
visit left of (Peter)
check self (Bob)
check self (Peter)
visit right of (Alice)
check self (Jane)
********** INORDER **********
visit left of (Alice)
visit left of (Peter)
visit right of (Alice)
check self (Jane)
Out[3]:
true

Divide and Conquer and the Towers of Hanoi

Let us consider the problem of the towers of Hanoi to illustrate the concept of divide and conquer which solves a complex problem by recursively dividing it into simpler subproblems whose solutions can be combined into a solution of the complex problem. The towers of Hanoi is a good example where a problem whose solution is non-obvious can be solved relatively easily using divide and conquer.

Towers of Hanoi

In the towers of Hanoi problem we have three stakes (left, middle, right) and a set of n discs of sizes n, n-1, ...,1. These discs are stacked in decreasing size on the left stake. To solve the towers of Hanoi one has to move the discs to the right stake such that they are ordered by increasing size. A valid move is taking the top disc from one of the stakes and moving it to a different stake such that it is not placed on top of a smaller disc. For instance, here is a solution for 3 discs:

Start with all 3 disc on the left stake

 1
 2
 3
 |   |   |

Move disc 1 to the right stake


 2
 3       1
 |   |   |

Move disc 2 to the middle stake



 3   2   1
 |   |   |

Move disc 1 to the middle stake


     1
 3   2   
 |   |   |

Move disc 3 to the right stake


     1
     2   3
 |   |   |

Move disc 1 to the left stake



 1   2   3
 |   |   |

Move disc 2 to the right stake


         2
 1       3
 |   |   |

Move disc 1 to the right stake

         1
         2
         3
 |   |   |

Moving 3 elements is relatively straight-forward. However, for a large number of elements it is not even clear immediatly whether it is even possible to solve the puzzle. Looking at the solution above we can make the following observation:

  • The process can be broken down into 3 coarser steps:
    1. move every disc except for the largest one to the middle stack, creating a stack of size n-1
    2. move the largest disc n to the right stack (which is now empty)
    3. move n-1 discs from the middle stack to the right stack

Note that this breaks down the problem of moving n discs into a subproblem of moving n-1 discs and one of moving 1 disc. Now, of course we have to move n-1 discs while there still exists a larger disc n on one of the stacks. However, since this disc is on the bottom of some stack and all n-1 discs are smaller this does not prevent us in any movement of the n-1 discs. That is, we are truely creating an independent subproblem of a smaller size. That is, the towers of Hanoi puzzle has a relatively easy recursive solution.

In [8]:
/**
 * 
 */
package lecture;

import java.util.Stack;
import java.util.List;
import java.util.ArrayList;


/**
 * @author lord_pretzel
 *
 */
public class TowersOfHanoi {


	private List<Stack<Integer>> stacks;
	private int numDiscs;
	int moves = 0;
	
	public TowersOfHanoi (int n) {
		this.numDiscs = n;
		stacks = List.of(new Stack<Integer>(), new Stack<Integer>(), new Stack<Integer>());
		Stack<Integer> left = stacks.get(0);
		for(int i = n; i > 0; i--)
			left.push(i);
	}

	public String toString() {
		StringBuilder result = new StringBuilder();
		int maxLen = Math.max(Math.max(stacks.get(0).size(), stacks.get(1).size()), stacks.get(2).size());

		for(int i = maxLen - 1; i >= 0; i--) {
			String line = String.format("%s   %s   %s\n", 
					(stacks.get(0).size() > i) ? stacks.get(0).get(i) : " ",
					(stacks.get(1).size() > i) ? stacks.get(1).get(i) : " ",
					(stacks.get(2).size() > i) ? stacks.get(2).get(i) : " "
					);
			result.append(line);
		}
		result.append("|   |   |");
		return result.toString();
	}

	public void solve() {
		System.out.println("initial configuration:\n\n" + toString());
		move(0, 2, numDiscs);
	}

	public void move(int from, int to, int n) {
        if (n == 0)
            return;
		System.out.printf("solve moving %d discs from %d to %d\n", n, from, to);
		int buf = getBuffer(from,to);

        move(from, buf, n-1);     // move n-1 discs: from -> buf
		moveSingleDisc(from, to); // move nth disc: from -> to
		move(buf, to, n-1);       // move n-1 discs: buf -> to
	}

	public void moveSingleDisc(int from, int to) {
		moves++;
		int disc = stacks.get(from).pop();
		stacks.get(to).push(disc);        
		System.out.printf("move disc %d from %d to %d:\n\n%s\n", disc, from, to, toString());
	}

	private int getBuffer(int from, int to) {
		if (from == 0 && to == 2)
			return 1;
		if (from == 0 && to == 1)
			return 2;
		if (from == 1 && to == 0)
			return 2;
		if (from == 1 && to == 2)
			return 0;
		if (from == 2 && to == 0)
			return 1;
		else // (from == 2 && to == 1)
			return 0;
	}

	public static void main(String[] args) {
		TowersOfHanoi h = new TowersOfHanoi(7);
		h.solve();
		System.out.printf("\n\n----------------------\nNumber of Moves: %d", 
				h.moves);
	}
}
Out[8]:
lecture.TowersOfHanoi
In [9]:
import lecture.TowersOfHanoi;

TowersOfHanoi.main(null);
initial configuration:

1        
2        
3        
4        
5        
6        
7        
|   |   |
solve moving 7 discs from 0 to 2
solve moving 6 discs from 0 to 1
solve moving 5 discs from 0 to 2
solve moving 4 discs from 0 to 1
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

2        
3        
4        
5        
6        
7       1
|   |   |
move disc 2 from 0 to 1:

3        
4        
5        
6        
7   2   1
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
4        
5        
6   1    
7   2    
|   |   |
move disc 3 from 0 to 2:

4        
5        
6   1    
7   2   3
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1        
4        
5        
6        
7   2   3
|   |   |
move disc 2 from 1 to 2:

1        
4        
5        
6       2
7       3
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

4        
5       1
6       2
7       3
|   |   |
move disc 4 from 0 to 1:

5       1
6       2
7   4   3
|   |   |
solve moving 3 discs from 2 to 1
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

5        
6   1   2
7   4   3
|   |   |
move disc 2 from 2 to 0:

2        
5        
6   1    
7   4   3
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1        
2        
5        
6        
7   4   3
|   |   |
move disc 3 from 2 to 1:

1        
2        
5        
6   3    
7   4    
|   |   |
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

2        
5        
6   3    
7   4   1
|   |   |
move disc 2 from 0 to 1:

5   2    
6   3    
7   4   1
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
5   2    
6   3    
7   4    
|   |   |
move disc 5 from 0 to 2:

    1    
    2    
6   3    
7   4   5
|   |   |
solve moving 4 discs from 1 to 2
solve moving 3 discs from 1 to 0
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   2    
6   3    
7   4   5
|   |   |
move disc 2 from 1 to 2:

1        
6   3   2
7   4   5
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
6   3   2
7   4   5
|   |   |
move disc 3 from 1 to 0:

3       1
6       2
7   4   5
|   |   |
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
6   1   2
7   4   5
|   |   |
move disc 2 from 2 to 0:

2        
3        
6   1    
7   4   5
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1        
2        
3        
6        
7   4   5
|   |   |
move disc 4 from 1 to 2:

1        
2        
3        
6       4
7       5
|   |   |
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

2        
3       1
6       4
7       5
|   |   |
move disc 2 from 0 to 1:

3       1
6       4
7   2   5
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
6   1   4
7   2   5
|   |   |
move disc 3 from 0 to 2:

        3
6   1   4
7   2   5
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       3
6       4
7   2   5
|   |   |
move disc 2 from 1 to 2:

        2
1       3
6       4
7       5
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        2
        3
6       4
7       5
|   |   |
move disc 6 from 0 to 1:

        1
        2
        3
        4
7   6   5
|   |   |
solve moving 5 discs from 2 to 1
solve moving 4 discs from 2 to 0
solve moving 3 discs from 2 to 1
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

        2
        3
    1   4
7   6   5
|   |   |
move disc 2 from 2 to 0:

        3
2   1   4
7   6   5
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       3
2       4
7   6   5
|   |   |
move disc 3 from 2 to 1:

1        
2   3   4
7   6   5
|   |   |
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
2   3   4
7   6   5
|   |   |
move disc 2 from 0 to 1:

    2   1
    3   4
7   6   5
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    2    
    3   4
7   6   5
|   |   |
move disc 4 from 2 to 0:

    1    
    2    
4   3    
7   6   5
|   |   |
solve moving 3 discs from 1 to 0
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   2    
4   3    
7   6   5
|   |   |
move disc 2 from 1 to 2:

1        
4   3   2
7   6   5
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
4   3   2
7   6   5
|   |   |
move disc 3 from 1 to 0:

3       1
4       2
7   6   5
|   |   |
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
4   1   2
7   6   5
|   |   |
move disc 2 from 2 to 0:

2        
3        
4   1    
7   6   5
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1        
2        
3        
4        
7   6   5
|   |   |
move disc 5 from 2 to 1:

1        
2        
3        
4   5    
7   6    
|   |   |
solve moving 4 discs from 0 to 1
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

2        
3        
4   5    
7   6   1
|   |   |
move disc 2 from 0 to 1:

3   2    
4   5    
7   6   1
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
3   2    
4   5    
7   6    
|   |   |
move disc 3 from 0 to 2:

    1    
    2    
4   5    
7   6   3
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   2    
4   5    
7   6   3
|   |   |
move disc 2 from 1 to 2:

1        
4   5   2
7   6   3
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
4   5   2
7   6   3
|   |   |
move disc 4 from 0 to 1:

    4   1
    5   2
7   6   3
|   |   |
solve moving 3 discs from 2 to 1
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    4    
    5   2
7   6   3
|   |   |
move disc 2 from 2 to 0:

    1    
    4    
2   5    
7   6   3
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   4    
2   5    
7   6   3
|   |   |
move disc 3 from 2 to 1:

    3    
1   4    
2   5    
7   6    
|   |   |
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

    3    
    4    
2   5    
7   6   1
|   |   |
move disc 2 from 0 to 1:

    2    
    3    
    4    
    5    
7   6   1
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    2    
    3    
    4    
    5    
7   6    
|   |   |
move disc 7 from 0 to 2:

    1    
    2    
    3    
    4    
    5    
    6   7
|   |   |
solve moving 6 discs from 1 to 2
solve moving 5 discs from 1 to 0
solve moving 4 discs from 1 to 2
solve moving 3 discs from 1 to 0
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

    2    
    3    
    4    
    5    
1   6   7
|   |   |
move disc 2 from 1 to 2:

    3    
    4    
    5   2
1   6   7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

    3    
    4   1
    5   2
    6   7
|   |   |
move disc 3 from 1 to 0:

    4   1
    5   2
3   6   7
|   |   |
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    4    
    5   2
3   6   7
|   |   |
move disc 2 from 2 to 0:

    1    
    4    
2   5    
3   6   7
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   4    
2   5    
3   6   7
|   |   |
move disc 4 from 1 to 2:

1        
2   5   4
3   6   7
|   |   |
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
2   5   4
3   6   7
|   |   |
move disc 2 from 0 to 1:

    2   1
    5   4
3   6   7
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    2    
    5   4
3   6   7
|   |   |
move disc 3 from 0 to 2:

    1    
    2   3
    5   4
    6   7
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

    2   3
    5   4
1   6   7
|   |   |
move disc 2 from 1 to 2:

        2
        3
    5   4
1   6   7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        2
        3
    5   4
    6   7
|   |   |
move disc 5 from 1 to 0:

        1
        2
        3
        4
5   6   7
|   |   |
solve moving 4 discs from 2 to 0
solve moving 3 discs from 2 to 1
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

        2
        3
    1   4
5   6   7
|   |   |
move disc 2 from 2 to 0:

        3
2   1   4
5   6   7
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       3
2       4
5   6   7
|   |   |
move disc 3 from 2 to 1:

1        
2   3   4
5   6   7
|   |   |
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
2   3   4
5   6   7
|   |   |
move disc 2 from 0 to 1:

    2   1
    3   4
5   6   7
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    2    
    3   4
5   6   7
|   |   |
move disc 4 from 2 to 0:

    1    
    2    
4   3    
5   6   7
|   |   |
solve moving 3 discs from 1 to 0
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1   2    
4   3    
5   6   7
|   |   |
move disc 2 from 1 to 2:

1        
4   3   2
5   6   7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
4   3   2
5   6   7
|   |   |
move disc 3 from 1 to 0:

3       1
4       2
5   6   7
|   |   |
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
4   1   2
5   6   7
|   |   |
move disc 2 from 2 to 0:

2        
3        
4   1    
5   6   7
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1        
2        
3        
4        
5   6   7
|   |   |
move disc 6 from 1 to 2:

1        
2        
3        
4       6
5       7
|   |   |
solve moving 5 discs from 0 to 2
solve moving 4 discs from 0 to 1
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

2        
3       1
4       6
5       7
|   |   |
move disc 2 from 0 to 1:

3       1
4       6
5   2   7
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

3        
4   1   6
5   2   7
|   |   |
move disc 3 from 0 to 2:

        3
4   1   6
5   2   7
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       3
4       6
5   2   7
|   |   |
move disc 2 from 1 to 2:

        2
1       3
4       6
5       7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        2
        3
4       6
5       7
|   |   |
move disc 4 from 0 to 1:

        1
        2
        3
        6
5   4   7
|   |   |
solve moving 3 discs from 2 to 1
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

        2
        3
    1   6
5   4   7
|   |   |
move disc 2 from 2 to 0:

        3
2   1   6
5   4   7
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       3
2       6
5   4   7
|   |   |
move disc 3 from 2 to 1:

1        
2   3   6
5   4   7
|   |   |
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
2   3   6
5   4   7
|   |   |
move disc 2 from 0 to 1:

    2   1
    3   6
5   4   7
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

    1    
    2    
    3   6
5   4   7
|   |   |
move disc 5 from 0 to 2:

    1    
    2   5
    3   6
    4   7
|   |   |
solve moving 4 discs from 1 to 2
solve moving 3 discs from 1 to 0
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

    2   5
    3   6
1   4   7
|   |   |
move disc 2 from 1 to 2:

        2
        5
    3   6
1   4   7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        2
        5
    3   6
    4   7
|   |   |
move disc 3 from 1 to 0:

        1
        2
        5
        6
3   4   7
|   |   |
solve moving 2 discs from 2 to 0
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

        2
        5
    1   6
3   4   7
|   |   |
move disc 2 from 2 to 0:

        5
2   1   6
3   4   7
|   |   |
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

1       5
2       6
3   4   7
|   |   |
move disc 4 from 1 to 2:

        4
1       5
2       6
3       7
|   |   |
solve moving 3 discs from 0 to 2
solve moving 2 discs from 0 to 1
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        4
        5
2       6
3       7
|   |   |
move disc 2 from 0 to 1:

        1
        4
        5
        6
3   2   7
|   |   |
solve moving 1 discs from 2 to 1
move disc 1 from 2 to 1:

        4
        5
    1   6
3   2   7
|   |   |
move disc 3 from 0 to 2:

        3
        4
        5
    1   6
    2   7
|   |   |
solve moving 2 discs from 1 to 2
solve moving 1 discs from 1 to 0
move disc 1 from 1 to 0:

        3
        4
        5
        6
1   2   7
|   |   |
move disc 2 from 1 to 2:

        2
        3
        4
        5
        6
1       7
|   |   |
solve moving 1 discs from 0 to 2
move disc 1 from 0 to 2:

        1
        2
        3
        4
        5
        6
        7
|   |   |


----------------------
Number of Moves: 127
Out[9]:
null

Runtime analysis

The recursive definition enables us to express the running time of the algorithm as a recusive equation:

$$ T(n) = T(n-1) + 1 + T(n-1) = 2 \cdot T(n-1) + 1 $$

To solve the problem for $n$ discs we have to solve the problem for $n-1$ discs twice and then move the largest disc (of size $n$) once. For a single disc we can solve the problem in one move:

$$ T(1) = 1 $$

To arrive at a closed form (a non-recursive version of this equation), we can compute the first few values to see whether a pattern emerges:

$$T(1) = 1$$$$T(2) = 3$$$$T(3) = 7$$$$T(4) = 15$$$$T(5) = 31$$

These numbers are all powers of $2$ minus $1$.

$$T(1) = 1 = 2^1 - 1$$$$T(2) = 3 = 2^2 - 1$$$$T(3) = 7 = 2^3 - 1$$$$T(4) = 15 = 2^4 - 1$$$$T(5) = 31 = 2^5 - 1$$

So we may hypothetize that $T(n) = 2^n-1$. To prove this hypothesis correct, we can use induction.

Base Case:

$$T(1) = 1 = 2^1 -1$$

Inductive Step:

$$T(n) = 2 \cdot T(n-1) + 1$$

$$= 2 \cdot (2^{n-1} - 1) + 1$$ $$= 2^n - 2 + 1\\ = 2^n - 1 $$