Recursion
Recursion is a programming technique in which a function calls itself to solve a problem by breaking it down into smaller subproblems.
Goals
By the end of this lesson, you should be able to:
- Solve problems using recursion.
- Identify problems that has recursive solutions.
- Identify base case and recursive case in a recursive problem and its solution.
- Explain and implement the recursive solution of Tower of Hanoi.
- Derive solution for recurrence of Tower of Hanoi using recursion-tree method.
recursion
, base case
, recursive case
, recursion tree
Introduction
Many computing problems are recursive in structure. With the word recursive we mean that to solve a given problem, they call themselves one or more times to solve a closely related problems. These algorithms in a way follow a divide-and-conquer approach.
Divide and conquer approach breaks the problem into several subproblems that are similar to the original problem but smaller in size. The algorithm then solves this smaller problems and then combines the solutions to create the solution to the original problem. The divide-and-conquer paradigm involves three steps at each level of the recursion:
- Divide the problem into a number of smaller problems.
- Conquer the smaller problems by solving them recursively and when the problem is small enough, hopefully, it can be solved trivially.
- Combine the solutions of the smaller problems into the solution for the original problem.
Summing The Elements of an Array
We will start giving example of divide-and-conquer approach by solving the following problem: summing a list of numbers in an array. Given a list of array, we can sum them by iterating over each element and sum each element to get the final solution. This iterative solution looks as follows.
def sum(array):
result = 0
for number in array:
result += number
return result
input_array = [4, 3, 2, 1, 7]
print(sum(input_array))
(C)ases
Another way of solving this problem can be done using a divide-and-conquer approach. In this approach we divide the input array into two parts. The first part consists of a single number while the second part consists of the rest of the numbers. For example, using the input in the above example, we have
[4, 3, 2, 1, 7]
The above input is now divided into two parts:
4 | [3, 2, 1, 7]
This is the divide step. In the conquer step, we recursively solve the second part using the same method. This means that we divide the second part into two parts as follows.
[3, 2, 1, 7]
becomes
3 | [2, 1, 7]
And this continues recursively
[2, 1, 7]
becomes
2 | [1, 7]
and
[1, 7]
becomes
1 | [7]
Now the second part consists of only a single number, i.e. 7. Since the problem is small enough, the solution to the sum problem is simply this number, which is 7. This means that the sum of an array with a single number is just that number. So we can now proceed to the combine step. We will combine the result by adding the number on the left with the sum of the array on the right.
1 | [7] = 1 + 7 = 8
and now we can move up
2 | [1, 7] = 2 + 8 = 10
similarly, we have these two summations
3 | [2, 1, 7] = 3 + 10 = 13
4 | [3, 2, 1, 7] = 4 + 13 = 17
(D)esign of Algorithm
Let's write down the steps on how we solve those particular cases.
Show Pseudocode
Input: Array or list of numbers
Output: the Sum of the array
Steps:
1. if the number of element is one only
1.1 Return that element as the sum of the array
2. Otherwise,
2.1 Return the addition of the first element with the sum of the rest of the array
Recursive Basic Structure
In all recursive solutions, we can identify the following characteristics:
- recursive solutions usually make use of functions that calls itself.
- these solutions have two general cases, the base case and the recursive case.
- the solutions are constructed using the same recursive code that is applied multiple time at different frames.
Let's go through this one by one.
The first is that recursive solutions usually make use of functions that calls itself. In the pseudocode above for summing a list of numbers, we can see that the solution to sum a list of numbers contains the step 2.1 which adds the first element to the sum of the rest of the array. To get the sum of the rest of the array, we can actually call the same function to compute the sum of a list of numbers. The only difference is that the array is smaller in this case because it excludes the first element. This can be implement by calling the same function as what is defined in this pseudocode but passing a different and smaller array from the original array.
The second characteristic is that we can always identify two cases:
- Base Case
- Recursive Case
In the algorithm steps above, step 1 is the base case. Base case is usually trivial and easy to solve. Recall that the strategy for this way of divide and conquer is to divide the problem into smaller problems up to the point that the problem becomes trivial. In this example, a trivial problem is to sum an array with only one element. In this case, the sum is just that element.
Step 2 is the recursive case. Notice in step 2.1 we are adding the first element with the sum of the rest of the array. The sum of the rest of the array is the same problem but with one element less. In this way, we have made the problem smaller. It is called recursive because this step calls the function itself with a smaller number of element in the array. How to break down and make the problem smaller will be one of the challenge in creating recursive solutions. Moreover, identifying the base case will also be important. Let's take a look at some more example in a later section. But for now, we will analyze the computation time.
It's important to highlight some characteristics of the base case and the recursive case. The solution to the base case is usually:
- trivial, and
- terminating
Trivial means that the solution is simple and easy to do. This is the reason why we want to break down the problem into smaller problems with the same substructure so that we can solve it easily. In our summation problem, the trivial solution is to sum a list of one element. The solution is trivial because the sum of one element is just that element itself.
Terminating means that the recursive process stops here at the base case. Otherwise, recursive solutions may never stops and we may reach and infinite recursive process. One indication it is a base case is that we do not have anywhere in this part of code that calls the recursive function again.
This is unlike the recursive case. In the recursive case, we do have some part of the code that actually calls the function again. We can call this function again because recursive case has two characteristics:
- it is the same problem
- it is a smaller problem
We can solve a problem using recursion if it has the same substructure when we break the problem into smaller problem. This means that the solutions involves the same problem. In the case of summing a list of numbers, we break the problem by adding the first element with the sum of the rest of the array. Summing the rest of the array is the same problem as the original problem.
The other characteristic is that the recursive case should solve a smaller problem and not the same problem. This is crucial because the aim is break down the problem into smaller problem which hopefully at the end will reach the base case. Therefore, there must be a way to reduce the problem into smaller problem. If not, we will never reach the base case and the recursion will never end. In our example of summing the list of numbers, we make the problem smaller by summing not the original array but rather the rest of the array except the first element. This reduces the size of the array by one. If we continue to apply the recursive case, we will reach the time when there is only one element left in the array and we have hit the base terminating case.
Computation Time
As you can guess from the iterative solution, this problem takes time. This means that as the number of input increases, the computation time increases linearly. How do we come about this computation time for recursive solution? One way to do this is to draw the recursion tree.
In a recursion tree, each node represents the cost of a single subproblem somewhere in the set of recursive function invocations. We then have to do two summations. The first sum is to sum all the cost on each level of the tree to obtain a set of cost-per-level. The second sum is to sum all cost-per-level over all levels to obtain the total cost.
To illustrate that, let's take a look at the example above on summing the elements in an array. Looking at the pseudocode, we see that the program will execute either step 1 or step 2 and not both. We can then make the following observation:
- if it is the base case, the computation time is because it takes constant time to check the case and constant time to return the element of an array.
- if it is the recursive case, the computation time is . The constant time comes from the addition operation which is the combine step. On the other hand, this computation time contains due to the recursive call for elements array.
The recursion tree can be drawn as follows.

The first figure on the left shows that the computation time for elements is a constant plus . The second figure in the middle shows the tree when we expland . That recursive call is when the input array is elements. The computation time when can be explanded as another plus . We can continue doing this until we are left only with one element, which is shown on the figure on the right with . The tree only has one child on each node and each level has the same cost which shown by the arrow pointing to the right. We can show that there are levels in the three for elements of input in the original call.
The cost for each level is a constant and we have levels, so the total cost is , and therefore we can say that the computation time for this recursion is .
Factorial Problem
Factorial problem can be defined recursively as follows:
(P)roblem Definition
Taking an integer input , the factorial can be calculated as follows:
We also define the factorial of the following input numbers:
Let's take a look at some particular examples.
(C)ases
Besides those two inputs 0 and 1, we can calculate, for example, the factorial of 5 as follows:
And we calculate 4 factorial with
Similarly,
and
but we have defined that . This is the base case. So we can calculate back up.
Now we can write the steps.
(D)esign of Algorithm
Show Pseudocode
Input: n, an integer
Output: factorial of n, an integer
Steps:
1. if n is equal to 0 or to 1
1.1 return 1
2. otherwise,
2.1 return n x factorial of n-1
Notice again here that step 1 is the base case which is trivial. The base case is also the terminating case. Without the base case the recursive solution will not end. So it is important to remember that every recursive solution must have a base case.
Step 2 is the recursive case. In this recursive case, we divide the problem of calculating factorial of into a smaller problem to calculate only the factorial of and a multiplication. Calculating the factorial of calls the function itself because it is exactly the same problem but with a smaller integer at the input. Let's discuss the computational time.
Computational Time
The computation time can be calculated as follows:
- To check whether it is the base case or not, it takes constant time .
- If it is the base case, it returns and exits the function immediately. So the computation time when it is a base case is simply constant time, i.e. .
- On the other hand, if it is not a base case, the computation time is . The constant time is the time it takes to multiply with the factorial of . The second term is the computation time to calculate the factorial of .
From the above describe, we have the same recursive tree as before.

From here, we can conclude that the computation time is linear, i.e. , a function of the input integer.
In the above two examples, summing an array and calculating a factorial, the solutions can be written either using recursion or iteration (such as a for-loop). It is not evident why we need a recursive solution in these two cases. However, there are cases when the iterative solution would be too complicated and its recursive solution is simple and elegant. One example of this is the problem of Tower of Hanoi which we will discuss next.
Tower of Hanoi Problem
Tower of Hanoi is a classic example of when its recursive solution is simple and elegant. The problem is specified as follows.
(P)roblem Definition
Given disks and three towers, one has to move the disks from the source tower to the *destination tower using the other *auxilary* tower. See figure below.

In the above figure, we have three towers A, B, and C. And we have to move disks from tower A to tower B using tower C as the auxiliary tower.
The rules in moving the disks are as follows:
- We can only move one disk at a time
- A bigger disk cannot be placed on top of a smaller disk
Note that can be 1 or greater.
(C)ases
Let's look at some particular cases. Let's start with a simple case when . In this case, to move one disk from A to B is trivial.
- Move disk 1 from A to B
and we are done.
How about when . Recall that our source tower is A and the destination tower is B while the auxiliary tower is C. In order to move two disks, we can do the following.
Moving two disks:
- Move disk 1 from A (source) to C (auxiliary)
- Move disk 2 from A (source) to B (destination)
- Move disk 1 from C (auxiliary) to B (destination)
Let's take a look when . To work this out, you can use the simulation for tower of Hanoi. We will divide this into a few step. The first step is to move the first two disks from A (source) to C (auxiliary). In order to move two disks, we already have the solution as shown above. So we can use the steps above with the difference in the destination and auxiliary towers.
Step 1
- Move disk 1 from A (source) to B (destination)
- Move disk 2 from A (source) to C (auxiliary)
- Move disk 1 from B (destination) to C (auxiliary)
Now we have the first two disks at C (auxiliary). The next step is to move disk 3 from A (source) to B (destination). This consists of only one step.
Step 2
- Move disk 3 from A (source) to B (destination)
The last step is to move the two disks from C (auxiliary) to the destination tower B. This again involves a similar three steps with differences in the source and destination towers. The steps to move the two disks to the final destination tower is as follows.
Step 3
- Move disk 1 from C (auxiliary) to A (source)
- Move disk 2 from C (auxiliary) to B (destination)
- Move disk 1 from A (source) to B (destination)
Notice that Step 1 and Step 3 involve the same steps as in Moving two disks. In fact, we can see Step 1 as Moving two disks with tower A as the source and tower C as the destination using tower B as the auxiliary tower. Similarly, we can see Step 3 as moving two disks with tower C as the source and tower B as the destination with tower A as the auxiliary tower. Moreover, the steps in Moving two disks are exactly the same three steps. We first move the top disks to the auxiliary tower, then the bottom disk to the destination tower, and lastly the top disks to the destination tower. In this way, we can use the same steps whenever there are more than 1 disks. In general, we can view this problem for disks as shown in the figure below.

where we divide the disks into two parts:
- the first top disks
- the last disk
We can then generalize the Tower of Hanoi problem to any number of disks as in the following steps.
(D)esign of Algorithm
Show Pseudocode
Input:
- n, number of disks
- source tower
- destination tower
- auxiliary tower
Output:
- sequence of steps to move n disks from source to destination tower using auxiliary tower
Steps:
1. if n is 1 disk:
1.1 Move the one disk from source to destination tower
2. otherwise, if n is greater than 1:
2.1 Move the first n - 1 disks from source to auxiliary tower
2.2 Move the last disk n from source to destination tower
2.3 Move the first n - 1 disks from the auxiliary tower to the destination tower
Notice that in the above steps, step 1 is the base case while step 2 is the recursive case. The base case is when the solution is trivial. This is the case when there is only one disk. The solution involves only one step which is to move that one disk from the source to the destination tower. On the other hand, step 2 is the recursive case. In this step, both steps 2.1 and step 2.3 are the recursive steps that reduce the problem smaller to disks. Step 2.2 consists of only a single step.
Looking into these steps and compare it with the examples in the previous steps, we observe that both the steps in moving two disks and the three steps in moving disks fall under the same recursive step 2. Both consist of three general steps 2.1, 2.2, and 2.3.
Computational Time
Now, let's analyze it's computational time. Looking into the steps above, we note the following:
- it takes to do the comparison whether it is one disk or more
- if it is only one disk, it takes time to produce the single step and exit the function
- otherwise, it takes time for step 2.1, and for step 2.2, and another for step 2.3
Let's draw the recursive tree for 3 disks.

Note that if , it takes constant time and computational time. In the recursive steps, each of this recursive call consists of another constant time and time. The tree stops at when it reaches the base case as there is only one disk left.
Now we can generalize this to disks. The recursive tree looks as the one below.

There will be levels from up to . Moreover, at each level, the sum total time is . If we sum up all the levels, we have the following series:
Recall that the sum for a Geometric series is given as follows
where is the constant of the series and is the ratio. In our case, and . So we have
This means that the computational time for the Tower of Hanoi problem is exponential with respect the input. As the number of input increases, the time it takes increases exponentially.
- when , it takes 1 step
- when , it takes 3 steps
- when , it takes 7 steps
- when , it takes 15 steps
- when , it takes 31 steps
Notice also that in this case as we can get the number of steps from .