Lecture 11: Recursion Chapter15

Objective

·        Distinguish between recursive and iterative solutions to a problem

·        Detect runaway (infinite) recursion

·        Explain how recursion can be used to reverse the order of a process

 

A recursive approach to a problem is one in which a function calls itself to arrive at a solution. An iterative approach to a problem is one in which a function contains a loop that is used to converge to a solution.

Generally, a recursive solution is slightly less efficient,  in terms of computer time, than an iterative one because of the overhead for the extra  function calls. In many instances, however,, recursion enables us to specify a natural, simple solution to a problem that otherwise would be difficult to solve. For this reason, recursion is an important and powerful tool in problem solving and programming. Recursion is used widely in solving problems. That are not numeric, such as proving mathematical theorems, writing compilers, and in searching and sorting algorithms.

Properties of Recursive Problems and solutions

Problems that can be solved by recursion have the following characteristics:

*. One or more stopping cases have a simple, nonrecursive solution

 

steps to solve a recursive problem

1-     Try to express the problem a s a simpler version of itself.

2-     Determine the stopping cases(base case)

3-     Determine the recursive steps(recursive case)

 

Stopping case: the statement that causes recursion to terminate

Terminating condition: a condition that evaluates a true when a stopping case is reached.

Recursive step: the step in a program or algorithm that contains a recursive call

In C, recursive functions typically have the following paradigmatic form

if (test for simple case) {

    compute a simple solution without using recursion  }

 else {

      break the problem down into subproblems of the same form.

solve each of the subproblems by calling this function recursively

reassemble the solutions to the subproblems into a solution for the whole

 

To use recursion, you must be able to identify simple cases for which the answer easily determined and a recursive decomposition that allows you to break any complex instance of the problem into simpler problems of the same type.

 

A recursive function must follow tow basic rules:

It must have and ending point

It must make the problem simpler

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


/* compute n! using a recursive definition */

/* iterative solutions computes n! for n is >=0 */

int factorial (int n)

{

 

if (n == 0)

    return 1;

else

    return( n * factorial (n-1));

 }

int factorial (int n)

{

int i, product=1;

for (i=n; i>1; --i)

/* n * (n-1) * (n-2)…*/

product=product *i; 

return (product); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Text Box: #include <stdio.h>
int main( )
 {
 int sum(int n);
 int num, s;
printf("enter an integer:");
scanf("%d",&num);
 s=sum(num);
 printf("%d",s);
return 0;
 }
  int sum(int n)
   {
      if (n <= 1)
        return n;
      else
      return n + sum(n-1); 
      }
Text Box: The Iterative
#include <stdio.h>
int main()
 {
 int sum(int n);
 int num,s;
printf("enter an integer:");
scanf("%d",&num);
 s=sum(num);
 printf("%d",s);
return 0;}
int sum( int n)
{
 int i, total=0;
for (i=1; i<=n ; i++)
total +=i;
return total;
}

 


Function call

n

n+ sum(n-1)

Return value

sum(4)

4

4 + sum(3)

4 + 6

4+ 6=10

sum(3)

3

3 + sum(2)

3 +  3

3+3=6

sum(2)

2

2 + sum(1)

2 +  1

2+1=3

sum(1)

1

1 + sum(0)

1  +  0

1+0=1

 

 

 

 

Power function . base exp. One way to do the calculation is:

Base exp= base * base * base * … * base

For exp repetitions. A more compact way is:

Base exp = base * base exp-1

In this case the solution to base exp is a larger case of solving base exp –1. The stopping condition ( the base case) occurs when exp=0. Since any number raised to the 0 power is 1, we have the following situation

 

 

 

 

Recursive

Iterative

int power(int base, int exp)

{

 if ( exp = = 0)

  return (1);

else

return (base * power(bae, exp –1));

}

 

int power_it(int base, int exp)

{

int count, product;

product=base;

for ( count = exp-1; count >0; count --)

product = product * base;

return ( product);

}      OR

 

int power(int base, int exp)

{ int p=1;

while(exp !=0)

{ p *= base;

exp --;

}

return p;

}

 


 Fibonacci Sequence:

The Fibonacci numbers are a sequence of numbers that have many varied uses

the Fibonacci sequence          0, 1, 1,2,3,5,8,13,21,34

Fibonacci number Begins with the term 0 and 1 and has the property that each succeeding term is the sum of the two preceding terms)

 

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 following functions implements computes the nth Fibonacci number.

Iterative Solution

Recursive  Solution

int fibonacci (int n)

{ int i,sum1=0, sum2=1, sum;

  if (n<=1)

     return n;

  else

  {  for (i=2; i<=n; i++)

     {   sum=sum1+sum2;

          sum1=sum2;

          sum2=sum;

     }

     return sum;

  }

}

int fibonacci(int n)

{  if (n<=1)

        return n;

    else

        return (fibonacci(n-1)

            + fibonacci(n-2));

}

 

  

#include <stdio.h>

int main( ) {

long result, number;

long fibonacci(long n);

printf("enter an integer:");

scanf("%ld",&number);

result=fibonacci(number);

printf("Fibonacci(%ld)=%ld\n",number,result);

return 0;

}

long fibonacci(long n)

{  long ans;

if( n ==0 || n ==1)

return n;

else

ans= fibonacci(n-1) + fibonacci (n -2);

return ans

Enter an integer:0

Fibonacci (0) =0

Enter an integer:3

Fibonacci (3) =2

Enter an integer:6

Fibonacci (6) =8

Enter an integer:5

Fibonacci (5) =5

Enter an integer:10

Fibonacci (10) =55

 

Problem: Write a recursive function that finds the sum of the values in an array x of size n(element x[0] through x[n-1]).

The stopping case occurs when n is 1, that is, the sum is x[0] for an array with one element. If n is not 1, then we must add x[n-1] to the sum we get when we add the values in the subarray with indices 0 through n-2(a simpler version of the problem)

 

#include<stdio.h>

int findsum( int x [ ], int n)

{

if (n <=1)

 return x[0];

 else

 return x[n-1] + findsum(x,n-1);

 }

void main( )

{ int array[3]={5,10,-7};

printf("the sum of the numbers=%d",findsum(array,3));

}

 

 

 

 

 

X[0]

X[1]

X[2]

5

10

-7


 

Example 4:  Reversing string.

An interesting feature of recursion is its ability to easily reverse a process.  The following program reads a string from the user and prints it backward.

 #include <stdio.h>

void reverse_str(void);

main( )

{   printf(“input a line:  “);

     reverse_str();

     printf(“\n\n”);

     return 0;

}

 

void reverse_str(void)

{   char ch;

     scanf(“%c”, &ch);

     if (ch != ‘\n’)

        reverse_str();

 

    printf(“%c”,ch);

}

 

q       Notice that in the function reverse_str, if the character read is not the new line character, the function is invoked again.

q       Each call has its own local storage for the variable ch.  The function calls are stacked by the system until the new line character is read.

q                   Only after the new line character is read that printing begins – starting with the last character entered.

Recursion relies on an internal (usually hardware) runt-time stack to store the information for keeping track of the recursive calls.

 

What happens if the recursive call is placed after printf as shown next?

void reverse_str(void)

{   char ch;

     scanf(“%c”, &ch);

     if (ch != ‘\n’)

          printf(“%c”,ch);

        reverse_str();

}

 

 


How Recursion Works: The Run-Time Stack

 

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       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))

 

 

 

 

 

 

 

 

 

 

 

 


Dynamic memory allocation occurs at run time. With each function call, an activation record is created and pushed onto the run –time stack.

An activation record contains the state of a given function, that is, the address of the current instruction and the values of all the local variables, Each time a function calls itself, an activation record is created and pushed on the  run-time stack . The first call thus appears at the bottom of the sack. The last call( the base case) appears at the top of the stack. At that time the top activation record is popped from the stack and the function returns its first value. The process continues until all activation records are popped from the stack, at which time the recursion stops

 


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.