Understanding Recursion in Programming: A Pseudocode Example and Comparison with Iteration, Exams of Computer Science

The concept of recursion in programming through a pseudocode example of a problem-solving function. It also compares recursion with iteration and provides examples of recursive and iterative solutions for factorial and exponentiation. Understand the nature of recursion, how to trace a recursive function, and the advantages and disadvantages of recursion.

Typology: Exams

Pre 2010

Uploaded on 11/08/2009

koofers-user-xwc-1
koofers-user-xwc-1 🇺🇸

10 documents

1 / 20

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Recursion - I
1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14

Partial preview of the text

Download Understanding Recursion in Programming: A Pseudocode Example and Comparison with Iteration and more Exams Computer Science in PDF only on Docsity!

Recursion - I

RECURSION

Recursion is a powerful problem-solving strategy. Solves large problems by reducing them to smaller problems of the same form. The subproblems have the same form as the original problem. To illustrate the basic idea, imagine you have been appointed as funding coordinator for a large charitable organization. Your job is to raise one million dollars in contributions to meet the expenses of the organization. If somebody can write out a check for the entire amount, your job is easy. On the other hand, it may not be easy to locate persons who would be willing to donate one million dollars. However people don’t mind donating smaller amounts. If lets say $100 is a good enough amount, than all you have to do is to call 10,000 friends to complete the task. Well, it is going to be a tall order for you, but you know that your organization has a reasonable supply of volunteers across the country. You may start off by finding 10 dedicated supporters in different parts of the country and appoint them regional coordinators. So each person now has to raise $100,000. It is simpler than raising one million, but hardly qualifies to be easy. Maybe they can adopt the same strategy, i.e. delegate parts of the job to 10 volunteers each within their region and asking each to raise $10,000. The delegation process can continue until there are volunteers who have to go around raising donations of $100 each from individual donors. The following structure is a psuedocode for the problem: Ask funding coordinator to collect fund void collect (int fund) { if ( fund <=100) { contact individual donor. } else { find 10 volunteers. Get each volunteer to collect fund/10 dollars Pool the money raised by the volunteers. } }

To make sure the process stops at some point, we define 0! to be 1. Thus the conventional mathematical definition looks like this: n! = 1 if n = 0 n! = n*(n-1)! if n > 0 This definition is recursive , because it defines the factorial of n in terms of factorial of n – 1. The new problem has the same form, which is, now find factorial of n – 1. In C: int fact(int n) { if (n ==0) return (1); else return (n * fact(n-1)); }

The Nature of Recursion

  1. One or more simple cases of the problem (called the stopping cases ) have a simple non- recursive solution.
  2. The other cases of the problem can be reduced ( using recursion ) to problems that are closer to stopping cases.
  3. Eventually the problem can be reduced to stopping cases only, which are relatively easy to solve. In general: if (stopping case) solve it else reduce the problem using recursion Tracing a Recursive Function Let us try to follow the logic the computer uses to evaluate any function call. It uses a stack to keep track of function calls. Whenever a new function is called, all its parameters and local variables are pushed onto the stack along with the memory address of the calling statement (this gives the computer the return point after execution of the function) In the factorial example , suppose “main” has a statement f= factorial (4); when main calls factorial, computer creates a new stack frame and copies the argument value into the formal parameter n. main fact if (n ==0) return (1); else return (n * fact(n-1)); n = 4

Because n is 0, the function parameter can return its result by executing the statement return(1); The value 1 is returned to the calling frame, which now becomes the top of stack as shown: Main fact1 fact2 fact3 fact if (n ==0) return (1); else return (n * factorial(n-1)); value 1 n = 1 Now the computation proceeds back through each of recursive calls. In above frame n=1 so it returns the value 1 to its caller , now the top frame shown here: Main fact1 fact2 fact if (n ==0) return (1); else return (n * factorial(n-1)); value 1 n = Because n is 2, a value 2 is passed back to previous level: Main fact1 fact if (n ==0) return (1); else return (n * factorial(n-1)); value 2 n = 3 Now the value returned is 3 x 2 to previous level as shown:

Main fact if (n ==0) return (1); else return (n * factorial(n-1)); value 6 n = 4 Finally the value 4 x 6 is calculated at 24 is returned to the main program.

Exponentiation:

Raising an integer to a power (which is also an integer) involves successive multiplication by the base. However, as the result becomes quite large very soon, it would work only if there is a machine to hold large integers. xn^ requires n-1 multiplications. power( x, n) { if ( n==0) return 1; if(n==1) return x; else return ( x * power( x , n – 1 ) ; } A more efficient iterative solution is possible. Note that a power 16 can be obtained by first computing power of 8 and multiplying the result with itself. Power of 8 can be obtained by multiplying two numbers of power of 4 each. Note the similarity with binary search. A higher power can be obtained from its lower power (i.e. power/2).

Evaluating a recursive function.

Consider the following recursive function: int puzzle (int N) { if (N == 1) return 1; if (N % 2 == 0) return (1 + puzzle(N/2)); else return (2 + puzzle(3N+1)); }* What do the following function calls evaluate to? a) puzzle(3); = return (2 + puzzle(10))

  • return (1+puzzle (5))
  • return (2+ puzzle(16))
  • return (1+puzzle(8)) +return (1+puzzle(4)) +ret(1+puzzle(2)) +ret(1+puzzle(1) +ret(1+1) +ret(1+2) +ret( 1 + 3) +ret(1+ 4) +ret( 2+5)
  • ret( 1+7) = ret(2+8) = b) puzzle(5); Answer: ____ 7 ___________

Recursive function to print a string in reverse order In the following program segment, we are trying to read a string and print it in reverse order. void print_reverse(int n) { char next; if (n == 1) { / stopping case / scanf("%c",&next); printf("%c", next); } else { scanf("%c", &next); print_reverse(n-1); printf("%c",next); } return; } int main() { printf("Enter a string: "); print_reverse(n); printf("\n"); }

Check whether a given string is a palindrome A palindrome is a string, whose first and last characters match AND the remaining substring is also a palindorme. Here is a C code which asks the user to supply a string and checks whether it is a palindrome or not. #include <stdio.h> #include <ctype.h> #include<string.h> int checkPal(char str[], int ); int main ( ) { char string[20] ; printf("\n Enter a string\n"); scanf("%s", string); if ( checkPal (str, strlen (str) )) printf("\nyes, it is a palindrome\n"); else printf("\nNo, it is not a palindrome"); } /function checkPal returns 1 if remaining characters in array str form a palindrome. / int checkPal ( char str[], int len) { if ( len <= 1) { return ( 1) ; } else { return ( (str [0] == str [ len - 1 ] ) && checkPal(str + 1, len - 2 ) ); } / array arithmetic used. New substring begins at str+1 and length chosen is 2 less than current length / }

Binary search ( recursive version) Divide and conquer implementation #include <stdio.h> int findkey (int key, int array[], int n ); int binary ( int key, int array [ ], int low, int high); int main ( ) { int key,check; int arrayd[11] = {12,24,30,45,51,65,70,76,82,91,96}; printf("\n Enter target key\n"); scanf("%d",&key); check= findkey(key,arrayd,11); if (check > -1) printf("\nyes, key found\n"); else printf("\nNo, not found\n"); } int findkey ( int key, int array[], int n ) { int temp; temp = binary(key, array, 0, n-1) ; return temp ; } int binary ( int key, int array [ ], int low, int high) { int mid; if ( low > high ) return (-1 ); mid = ( low + high )/ 2; printf("\nmid=%d\n",mid); if ( key == array [mid] ){ return (mid);} if ( key < array [mid]) { return (binary (key, array, low, mid - 1 )); } else { return (binary (key, array, mid + 1, high )) ; } }

Fibonacci Sequence

It has a small history. In 1202, Italian mathematician Leonardo Fibonacci posed a problem that has had a wide influence on many fields. The problem is related to growth in population of rabbits, generation to generation. The rabbits are reproduced according to the following rules:

  1. Each pair of fertile rabbits produces a new pair of offspring each month.
  2. Rabbits become fertile in their second month of life.
  3. No rabbit dies. Suppose we start with a pair introduced in January. Thus on Jan 1 , no rabbits and on Feb 1 , one pair. Since they are not yet fertile on March 1, still one pair. In march they produce another pair, so on April 1, the count is 2 pairs. In April the original pair produces another pair, so on May 1 there are 3 pairs. Then…… We start getting the Fibonacci sequence: It is the sequence of integers: t 0 t 1 t 2 t 3 t 4 t 5 t 6 t 7 t 8 t 9 0 1 1 2 3 5 8 13 21 34 … Interesting observation: Each element in this sequence is the sum of the two preceding elements. The specification of the terms in the Fibonacci sequence: n if n is 0 or 1 (i.e. n < 2) tn = tn-1+ tn-2 otherwise How fast do the numbers grow? If 4th^ term is 2, 7th^ term is 8, 12th^ term is 89, 20th^ term is 4181….. It can be shown by induction that tn > ( 3/2)n tn = tn-1+ tn- tn > ( 3/2)n-1^ + ( 3/2)n-

(2/3) (3/2) ( 3/2)n-

  • (2/3) (3/2) (2/3) (3/2) ( 3/2)n-

(2/3) ( 3/2)n

  • (2/3) (2/3) ( 3/2)n

[(2/3) + (4/9) ] ( 3/2)n (10/9) ( 3/2)n tn > ( 3/2)n In C: int fibonacci(int n) { if (n < 2) return n; else return(fibonacci(n-2) + fibonacci(n-1)); } Calling the function : x = fibonacci(5); What is the complexity of this function? At every call , two more fibonacci functions are being called. Let T(n) be running time for calling the fib. Function. For n =0 or 1 it takes constant time T(0) = T(1) = 1 At any other stage, it involves calling fib function with running time T(n-1) and again with running time T(n-2). In addition, there is a “if” operation and one “addition” operation. Thus, T(n) = T(n-1) + T(n-2) + 2 Since fib (n) = fib (n-1) + fib (n-2) it is easy to see that T(n) > fib(n)

Complexity of the efficient recursive algorithm for

exponentiation:

power( x, n) { if ( n==0) return 1; if(n==1) return x; if (n is even) return power ( x * x, n / 2); else return power( x * x, n / 2 ) * x ; } At every step the problem size reduces to half the size. When the power is an odd number, an additional multiplication is involved. To work out time complexity , let us consider the worst case, i.e. at every step an additional multiplication is needed. T(n) = T(n/2) + 1 = [ T(n/4) + 1 ] + 1 = [ T(n/8) + 1 ] + 1 + 1 = T( n/ 2^3 ) + 3

....... ....... = T( n/ 2k) + k Choosing 2 k= n T(n) = log n + 1 Thus the order of complexity is O( log n) LOGARTHIMIC TIME

Common Errors in Recursive Formulations

 It may not terminate if the stopping case is not correct or is incomplete (stack
overflow: run-time error)
 Make sure that each recursive step leads to a situation that is closer to a
stopping case.

Comparison of Iteration and Recursion

 In general, an iterative version of a program will execute more efficiently in
terms of time and space than a recursive version. This is because the overhead
involved in entering and exiting a function is avoided in iterative version.
 However a recursive solution can be sometimes the most natural and logical
way of solving a problem.
 Conflict: machine efficiency versus programmer efficiency
 It is always true that recursion can be replaced with iteration and a
stack.