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.