Lecture 11:  Recursive functions and efficiency

 

Objectives of this lecture

q       Learn how computer handles recursive functions.

q       Learn why recursive functions are generally inefficient.

q       Learn how to use static variables and pointer parameters with recursive functions.

 

How recursion works?

q       Recursion is very attractive as it can be used to write more elegant functions that require less number of variables than their equivalent iterative versions.

q       However, recursive functions can be very inefficient and often make programs to crash.  We explain this by studying how computer handles recursive call.

q       When a function (not necessarily recursive) is called by another function, the computer normally keeps the following information:

Ø      The address of the instruction following the call – so that the program knows where to return to when the function call is complete

Ø      The values of all local and output variables.

q       Except for recursive functions, storage can be allocated for these information at compile time – since the compiler can determine where each function is called.  This is call static memory allocation.

q       However, for recursive functions, it is not possible to determine this at compile time since the number of times a function will call itself can only be know at run-time.

q       The allocation is thus done at run-time each time a function call itself.  This is called dynamic memory allocation.

q       With each recursive call, an activation record (AR) holding the necessary information is created and pushed onto the run-time stack – a special memory area.

 

Example 1: The factorial function

q       The following diagram shows what happened when the factorial function developed in the previous lecture is called with n=4


 

AR5

          n=0

          return (1)

AR4

          n=1

          return (1*fact(0))

AR3

          n=2

          return (2*fact(1))

AR2

          n=3

          return (3*fact(2))

AR1

          n=4

          return (4*fact(3))

 

q       An AR is created and pushed onto the stack each time the function calls itself

q       For large value of n, the stack can easily be exhausted leading to the crash of the program.

q       Also, considerable amount of time is taken in pushing and popping ARs onto and off the stack.

q       All these make recursive functions generally less efficient than their iterative counterpart.

 

Example 2:  Fibonacci Sequence:

This is defined recursively as:

f0 = 0,                  f1 = 1          fi+1 = fi + fi-1  for i=1,2, ..

i.e. except for f0 and f1, every element is the sum of its previous two elements:  0, 1, 1, 3, 5, . . .

The recursive implementation is as follows:

int fibonacci(int n)

{  if (n<=1)

        return n;

    else

        return (fibonacci(n-1) + fibonacci(n-2));

}

As the following table shows, a large number of function calls is required by the above function in computing the nth fibonacci  number.

 

n

Value of fibonacci(n)

Number of calls

0

0

1

1

1

1

2

1

3

3

2

5

. . .

. . .

. . .

8

21

67

9

34

109

. . .

. . .

. . .

25

75,025

242,785

 

q       Clearly, even for moderately small numbers, the number of activation records that will be created is so large that the program can easily crash.

q       This suggests that even though recursion is elegant, we should be careful in how we use it.

q       However, static and pointer variables can be used to improve the efficiency of some recursive function.

 

Recursive function involving static and pointer variables:

Static variables may be used to retain values of variables from successive calls of a recursive function. 

 

Example1:

The following program computes the sum of the series : 1+2+3+4+..

up to the nth term:

#include <stdio.h>

 

int get_sum(int n)

{  if (n<1)

      return 0;

   else

      return (n+get_sum(n-1));

}

 

main()

{  int num;

   printf("Enter your number>");

   scanf("%d",&num);

   printf("the sum to %dth term is %d\n",num,get_sum(num));

   return 0;

}

Example2:

The following modifies the above program to use static variable.

#include <stdio.h>

int get_sum(int n)

{  int static sum=0;

    if (n>0)

   {  sum+=n;

       get_sum(n-1);

   }

   return sum;

}

 

main()

{  int num;

   printf("Enter your number>");

   scanf("%d",&num);

   printf("the sum to %dth term is %d\n",num,get_sum(num));

   return 0;

}

 

Example 3: 

The following modifies the program above to use pointer variables:

#include <stdio.h>

 

void get_sum(int n, int *sum)

{  if (n>0)

   {   *sum+=n;

       get_sum(n-1,sum);

   }

}

 

main()

{  int num,sum;

   printf("Enter your number>");

   scanf("%d",&num);

   get_sum(num,&sum);

   printf("the sum to %dth term is %d\n",num,sum);

   return 0;

}

 

Comparison between iterative and recursive functions:

q       Loops in the iterative case are replaced by if statement in the recursive case

q       The terminating condition for loop becomes the condition for the if statement

q       Variables that are repeatedly changed in the loop can be made static or passed as extra arguments in the recursive version

 

Tutorial exercise:

q       In the reverse string function, what happens if the recursive call is placed after printf:

 void reverse_str(void)

{   char ch;

    scanf(“%c”, &ch);

    if (ch != ‘\n’)

      printf(“%c”,ch);

   reverse_str();

}

q       Write a recursive function that prints help exactly n times