Tower of Hanoi is a classic puzzle in which you need to transfer a set of discs from one pole to another pole using a spare pole. It has three simple rules:
Let’s develop an algorithm for playing this game, and specifically a function
move_tower that has parameters
height is the number of discs to move from the
from_pole to the
to_pole. A hint: there is an elegant recursive solution
(check out hanoi.py).
As a starting example consider computing the factorial. A natural iterative solution is below. How could we approach this problem recursively?
def factorial(n): result = 1 for i in range(2, n+1): result *= i return result
A recursive algorithm is defined in terms of solutions to smaller versions of the same problem. A recursive function, then calls itself to solve a smaller version of the problem.
Let’s think about solving
factorial recursively. Can we break down the
factorial problem into a computation and an identical sub-problem?
5! = 5 * 4 * 3 * 2 * 1 5! = 5 * 4!
A first attempt at a recursive factorial function:
def factorial(n): return n * factorial(n-1)
Let’s visualize the call stack:
5 * factorial(4) | 4 * factorial(3) | 3 * factorial(2) | 2 * factorial(1) | 1 * factorial(0) | 0 * factorial(-1) | ...
So when will this end? Never! At some point we need to terminate the recursion. We call that the base case. The base case and the recursive relationship are the two key elements of any recursive algorithm.
For factorial, we know that
factorial(1) == 1 (and
factorial(0) == 1) so:
def factorial(n): if n <= 1: return 1 else: return n * factorial(n-1)
Here we see the typical structure of a recursive function: First we check if we are at the base case(s), if so return the result directly. If not, we invoke the function recursively on a sub-problem.
Clearly this works. But why? Doesn’t each call to
No. To help us understand what happens when we call a function (and what we
mean by the call stack) let’s use Python Tutor on our
Whenever we invoke a function we create a new “frame” on the “call stack” that contains the arguments (local variables and other state in the function). Thus we don’t “overwrite” the parameters when we repeatedly invoke our function.
We employ a 4 step process:
Recursion has a similar feel to “induction” in mathematics:
Let’s use this process to recursively reverse a string (check it out in Python Tutor):
Define the function header, including the parameters
Define the recursive case
Assume we have a working reverse function that can only be called on smaller strings. To reverse a string:
Define the base case
The reverse of the empty string is just the empty string.
Put it all together
def reverse(a_string): if len(a_string) == 0: return "" else: return reverse(a_string[1:]) + a_string
An implementation note … Why doesn’t
a_string[1:] produce an index error
a_string is a single letter (e.g.
"e"[1:])? Slicing has the nice
property that slicing beyond the end of the string evaluates to the empty
>>> "e"[1:] ''
However, indexing a single value (not slicing) beyond the end of a string (or list) will produce an error, e.g.
>>> "e" Traceback (most recent call last): File "<pyshell>", line 1, in <module> IndexError: string index out of range