When Recursion should not be used
Objectives of this lecture
q Learn how to improve
recursive functions by removing tail recursion
q Lean when recursion is
better replaced by iteration.
Tail Recursion
q Recall that when a function
calls itself (or another function), the local variables of that function are
pushed on the stack. When the recursive
call terminates, these variables are popped and restored to their original
state.
q If the recursive call
happens to be the last statement in the function, a situation called tail
recursion,
these variables are destroyed immediately after restoring them – why?.
q It is obvious therefore,
that there is no need to push these variables in the stack in the first place.
It is thus, desirable to avoid tail recursion.
q We can avoid tail recursion
by re-assigning the calling parameters to the values specified in the recursive
call and repeating the whole function using a do while or while statement.
q For example, the function move in the Towers of Hanoi
problem can be improved by removing the tail recursion as follows:
/*
Move: iterative version.
Pre: Disk count is a valid disk to be moved.
Post:
Moves count disks from start to finish using temp for temporary storage.
*/
void
Move(int count, int start, int finish, int temp)
{ int swap;
/* temporary storage to swap towers */
while (count > 0)
{
Move(count - 1, start, temp, finish);
printf("Move disk %d from %d to
%d.\n", count, start,
finish);
count--;
swap = start;
start = temp;
temp = swap;
}
}
q We can interpret this as: To
move a stack of n discs onto finish, we must move all except the bottom to one of finish
or temp, then move the bottom one to finish, and repeat after interchanging
start with temp.
When iteration is better than recursion:
q The simplest way to
determine whether recursion should be used, or replaced by iteration, is by
drawing the recursive tree.
q If the recursive tree of a
recursive function is simple (with no branches), then the function can easily
be implemented using iteration, and since iteration requires less memory, it
should be used.
q For example, the recursive
tree of the factorial function is shown below.
n!
(n-1)!
(n-2)!
1!
0!
q It is clearly a waste of
memory to use recursion in this case.
The following shows the iterative version.
/*
Function: iterative version.
Pre: n is a nonnegative integer.
Post:
The function value is the factorial of n.
*/
int
Factorial(int n)
{
int count, product;
product = 1;
for (count = 2; count <= n; count++)
product *= count;
return product;
}
q Another situation where
recursion should be avoided is where it makes unnecessary duplications. For example, consider the following
recursive function that implements the Fibonacci sequence.
/*
Fibonacci: recursive version.
Pre: The parameter n is a nonnegative integer.
Post:
The function returns the n^th Fibonacci number.
*/
int
Fibonacci(int n)
{
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return Fibonacci(n-1) +
Fibonacci(n-2);
}
q The amount of time required
to compute F(n) grows exponentially with n as the following recursive tree shows:
q The iterative version can be
obtain by simply using two variables to store that last two values as shown
below:
/*
Fibonacci: iterative version.
Pre: The parameter n is a nonnegative integer.
Post:
The function returns the n^th Fibonacci number.
*/
int
Fibonacci(int n)
{
int i;
int twoback; /* second previous number, F_i-2 */
int oneback; /* previous number, F_i-1
*/
int current; /* current number, F_i
*/
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else {
twoback = 0;
oneback = 1;
for (i = 2; i <= n; i++)
{
current = twoback + oneback;
twoback = oneback;
oneback = current;
}
return current;
}
}
q If a the recursive tree of a
function appears bushy (many children), with little duplication to tasks, then
recursion is likely to be the natural method.
Exercise:
try exercises E2 and E4 of pages 121-122 of your book.