Presentation is loading. Please wait.

Presentation is loading. Please wait.

1 CS 410 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS status 7/3/2011.

Similar presentations


Presentation on theme: "1 CS 410 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS status 7/3/2011."— Presentation transcript:

1 1 CS 410 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS status 7/3/2011

2 2 Syllabus Definition of Recursion Definition of Recursion Recursion vs. Iteration Recursion vs. Iteration Q-Sequence Q-Sequence Ackermann Function Ackermann Function Simulate Recursion via Iteration Simulate Recursion via Iteration References References

3 3 Definition of Recursion An algorithms is recursive, if it is partly defined in simpler versions of itself [1] A recursive program is the implementation of a recursive algorithm What if a programming language is non-recursive (e.g. Fortran) and we wish to express a recursive algorithm (e.g. parsing) in that very language? --See later! What then are the other parts of a recursive algorithm? A correctly defined recursive algorithm requires a starting point, formally known as the “base case” Base case could be multiple steps Recursive algorithm A() uses a base case as origin of computation, plus the actual, recursive function body, including some recursive use of A() Recursive body can be indirectly recursive through intermediate function a()-> b()-> a() – through intermediate function b() Primitive examples are the factorial( n ) function; or Fibonacci( n ), for non-negative arguments n; the latter shown here: Base case 1:Fibo(0) = 0 Base case 2:Fibo(1) = 1 Recursive Definition:Fibo( n ) for n > 1 = Fibo( n-1 ) + Fibo( n-2 )

4 4 Recursion vs. Iteration Iteration is expressed in programming languages by loops; e.g. for-, while-, do-, repeat loops These are readable and efficient methods for expressing iteration, but are not necessary Recursion can easily replace such iterative steps; yet for some people this seems hard to understand In reality, people are simply unused to recursion; else recursion would be as immediately understandable as iteration

5 5 Q-Sequence, Definition Q-Sequence is defined in [1] as a function q(n) for positive integers n Base case 1: q(1) = 1 Base case 2: q(2) = 1 Recursive definition of q(n), for n > 2 q( n ) = q( n – q( n-1 ) ) + q( n – q( n-2 ) ) Q-Sequence reminds us of Fibonacci( n ) function, but with a great difference in type of result: The function results of Fibonacci( n ) are monotonically increasing with increasing argument Yet surprisingly, the results of q( n ) are non-monotonic! Note # of calls: calls( q( 40 ) ) = 1,137,454,741

6 6 Q-Sequence, Coded in C #define MAX 100// arbitrary limit int calls;// will be initialized each time int q( int arg ) { // q calls++;// track another call if ( arg <= 2 ) { return 1;// base case return 1;// base case }else{// now recurse! return q( arg - q( arg-1) ) + q( arg - q( arg-2) ); return q( arg - q( arg-1) ) + q( arg - q( arg-2) ); } //end if } //end q void main() { // main for ( int i = 1; i < MAX; i++ ) { calls = 0;// initially no calls yet calls = 0;// initially no calls yet printf( "Q(%2d) = %3d, #calls = %10d\n", i, q(i), calls ); printf( "Q(%2d) = %3d, #calls = %10d\n", i, q(i), calls ); } //end for } // end main

7 7 Q-Sequence Results Q( 1) = 1, #calls = 1 Q( 2) = 1, #calls = 1 Q( 3) = 2, #calls = 5 Q( 4) = 3, #calls = 13 Q( 5) = 3, #calls = 25 Q( 6) = 4, #calls = 49 Q( 7) = 5, #calls = 93 Q( 8) = 5, #calls = 161 Q( 9) = 6, #calls = 281 Q(10) = 6, #calls = 481 Q(11) = 6, #calls = 813...... Q(26) = 14, #calls = 1341433 Q(27) = 16, #calls = 2174493 Q(28) = 16, #calls = 3521137 Q(29) = 16, #calls = 5700281 Q(30) = 16, #calls = 9229053 Q(31) = 20, #calls = 14941993 Q(32) = 17, #calls = 24182797 Q(33) = 17, #calls = 39137473 Q(34) = 20, #calls = 63354153 Q(35) = 21, #calls = 102525697 Q(36) = 19, #calls = 165896537 Q(37) = 20, #calls = 268460333 Q(38) = 22, #calls = 434429737 Q(39) = 21, #calls = 702952137 Q(40) = 22, #calls = 1137454741... Will never reach 100 in your life time... Will never reach 100 in your life time

8 8 Ackermann Definition Ackermann a( m, n ) is defined as a function of two non-negative integers m and n Base case 1: a( 0, n ) = n+1 Base case 2: a( m, 0 ) = a( m-1, 1 ) Recursive definition of a( m, n ), m, n > 0 a( m, n ) = a( m-1, a( m, n-1 ) ) Ackermann grows awfully fast. For example: a(4,2) is integer with 19,729 decimal digits

9 9 Ackermann Coded in C unsigned a( unsigned m, unsigned n ) { // a calls++;// global unsigned if ( 0 == m ) { return n + 1;// a base case return n + 1;// a base case }else if ( 0 == n ) {// m > 0 return a( m-1, 1 );// other base case return a( m-1, 1 );// other base case }else{// m > 0, n > 0 return a( m-1, a( m, n-1 ) );// recurse return a( m-1, a( m, n-1 ) );// recurse } //end if } //end q void main() { // main for( int i = 0; i < MAX; i++ ) { printf( "\nFor m = %d\n", i ); printf( "\nFor m = %d\n", i ); for( int j = 0; j < MAX; j++ ) { for( int j = 0; j < MAX; j++ ) { calls = 0; calls = 0; printf( "a(%1d,%1d) = %10u, calls = %12u\n", printf( "a(%1d,%1d) = %10u, calls = %12u\n", i, j, a( i, j ), calls ); i, j, a( i, j ), calls ); } //end for } //end for } //end for } // end main

10 10 Ackermann Results For m = 0 a(0,0) = 1, calls = 1... For m = 1... a(1,7) = 9, calls = 16 For m = 2 a(2,0) = 3, calls = 5 a(2,1) = 5, calls = 14 a(2,2) = 7, calls = 27 a(2,3) = 9, calls = 44 a(2,4) = 11, calls = 65 a(2,5) = 13, calls = 90 a(2,6) = 15, calls = 119 a(2,7) = 17, calls = 152 For m = 3 a(3,0) = 5, calls = 15 a(3,1) = 13, calls = 106 a(3,2) = 29, calls = 541 a(3,3) = 61, calls = 2432 a(3,4) = 125, calls = 10307 a(3,5) = 253, calls = 42438 a(3,6) = 509, calls = 172233 a(3,7) = 1021, calls = 693964 For m = 4 a(4,0) = 13, calls = 107 don’t even dream about computing a(4,2) or higher!

11 11 Simulate Recursion via Iteration What to do, if you implement a recursive algorithm using a language that does not support recursion? Replace the recursive by a non-recursive algorithm! Or simulate recursion via non-recursive methods After all, a computer chip has no notion of recursion; it is a sequential machine that “simulates recursion” via non-recursive methods; compiler maps! Done so frequently in industry; e.g. FPS used Fortran to implement System SW and compilers Here are the actual steps of simulating recursion via iteration:

12 12 Steps of Simulating Recursion consider directly-recursive calls, i.e. calls from within recursive function:  Define explicit stack with top of stack (tos) index, initially 0; like a real stack, it may overflow, include code to check; holds all parameters, function return value, return location (labels after a recursive call)  Define labels for each point of recursive call, and each point of return; number these labels, for example l1, l2, l3 etc. There will be branches to the points of return  At each point of recursive call: Increment the tos Move parameters for “this call” onto stack; e.g. stack[ tos ].arg1 = … Store place of return; e.g. stack[ tos ].ret = 1, 2, 3 alluding to labels l1, l2, l3 Jump to the head of the function, not including initializing code

13 13 Steps of Simulating Recursion 4. Ideally, all explicitly coded returns and the implied return at the end of the recursive function body can be re-coded into a single place; if not, the code before each return is replicated: Decrement the top of stack index Check, to which of the stored labels the flow of control has to branch to continue execution; e.g.: if ( stack[ tos ].ret == xyz ) goto label_xyz; And if no other branch is open, then fall through the end For void functions this is a literal fall-through For true functions, the return value has to be computed, e.g.: stack[ tos ].return_val = … 5. For nested recursive calls or several recursive calls in a row or both: “be creative” 5. For nested recursive calls or several recursive calls in a row or both: “be creative”

14 14 Simulate Recursion, fact() #include #include #define MAX_STACK 100// never reached or exceeded! #define MAX 14// higher factorials overflow 32b unsigned calls;// in case we track # of calls typedef struct s_tp { unsigned arg; unsigned fact; unsigned ret; } struct_s_tp; // first the recursive fact() function for reference // includes tracking # of calls unsigned fact( unsigned arg ) { // fact calls++;// gotta be global if ( 0 == arg ) { return 1; return 1;}else{ return fact( arg - 1 ) * arg; return fact( arg - 1 ) * arg; } //end if } //end fact

15 15 Simulate Recursion, fact() unsigned nrfact( unsigned arg ) { //nrfact struct_s_tp s[ MAX_STACK ];// local stack, no recursion! unsigned top = 0; s[ top ].arg = arg;// this call’s argument s[ top ].ret = 3;// 3 alludes to label l3 l1:if ( 0 == s[ top ].arg ) { s[ top ].fact = 1; s[ top ].fact = 1;}else{ top++;// recurse soon top++;// recurse soon s[ top ].arg = s[ top-1 ].arg-1; s[ top ].arg = s[ top-1 ].arg-1; s[ top ].ret = 2;// remember label l2 s[ top ].ret = 2;// remember label l2 goto l1;// here simulate recursion goto l1;// here simulate recursionl2: // back from recursive call. // back from recursive call. top--;// back from call top--;// back from call s[ top ].fact = s[ top + 1 ].fact * s[ top ].arg; s[ top ].fact = s[ top + 1 ].fact * s[ top ].arg; } //end if if ( s[ top ].ret == 2 ) {// test, where to branch to goto l2;// unstructured goto into if goto l2;// unstructured goto into if } //end if l3: return s[ top ].fact; } //end nrfact

16 16 Simulate Recursion, fact() Results r_fact( 0) = 1, calls = 1 r_fact( 1) = 1, calls = 2 r_fact( 2) = 2, calls = 3 r_fact( 3) = 6, calls = 4 r_fact( 4) = 24, calls = 5 r_fact( 5) = 120, calls = 6 r_fact( 6) = 720, calls = 7 r_fact( 7) = 5040, calls = 8 r_fact( 8) = 40320, calls = 9 r_fact( 9) = 362880, calls = 10 r_fact(10) = 3628800, calls = 11 r_fact(11) = 39916800, calls = 12 r_fact(12) = 479001600, calls = 13 r_fact(13) = 1932053504, calls = 14 nr_fact( 0) = 1 nr_fact( 1) = 1 nr_fact( 2) = 2 nr_fact( 3) = 6 nr_fact( 4) = 24 nr_fact( 5) = 120 nr_fact( 6) = 720 nr_fact( 7) = 5040 nr_fact( 8) = 40320 nr_fact( 9) = 362880 nr_fact(10) = 3628800 nr_fact(11) = 39916800 nr_fact(12) = 479001600 nr_fact(13) = 1932053504 r_fact( 0) = 1, calls = 1 r_fact( 1) = 1, calls = 2 r_fact( 2) = 2, calls = 3 r_fact( 3) = 6, calls = 4 r_fact( 4) = 24, calls = 5 r_fact( 5) = 120, calls = 6 r_fact( 6) = 720, calls = 7 r_fact( 7) = 5040, calls = 8 r_fact( 8) = 40320, calls = 9 r_fact( 9) = 362880, calls = 10 r_fact(10) = 3628800, calls = 11 r_fact(11) = 39916800, calls = 12 r_fact(12) = 479001600, calls = 13 r_fact(13) = 1932053504, calls = 14 nr_fact( 0) = 1 nr_fact( 1) = 1 nr_fact( 2) = 2 nr_fact( 3) = 6 nr_fact( 4) = 24 nr_fact( 5) = 120 nr_fact( 6) = 720 nr_fact( 7) = 5040 nr_fact( 8) = 40320 nr_fact( 9) = 362880 nr_fact(10) = 3628800 nr_fact(11) = 39916800 nr_fact(12) = 479001600 nr_fact(13) = 1932053504

17 17 Simulate Recursion, fibo() #define MAX_STACK 100// never to be reached or exceeded! #define MAX 30// higher fibo(n) not computed unsigned calls;// in case we track # of calls typedef struct s_tp {// type of stack unsigned arg;// copy of fibo’s arg unsigned fibo;// return value for fibo unsigned ret;// to which label to goto? } struct_s_tp; // recursive function for reference: unsigned fibo( unsigned arg ) { // fibo calls++; if ( arg <= 1 ) {// base case? return arg;// if so: done return arg;// if so: done}else{ return fibo( arg-1 ) + fibo( arg-2 ); return fibo( arg-1 ) + fibo( arg-2 ); } //end if } //end fibo

18 18 Simulate Recursion, fibo() unsigned nr_fibo( unsigned arg ) { //nr_fibo struct_s_tp s[ MAX_STACK ];// stack can be local struct_s_tp s[ MAX_STACK ];// stack can be local unsigned top = 0;// initially unsigned top = 0;// initially s[ top ].arg = arg;// copy arg to stack s[ top ].arg = arg;// copy arg to stack s[ top ].ret = 4;// if all fails, return s[ top ].ret = 4;// if all fails, return l1: if ( s[ top ].arg <= 1 ) { s[ top ].fibo = s[ top ].arg; s[ top ].fibo = s[ top ].arg; }else{ }else{ top++;// ready to recurse top++;// ready to recurse s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].ret = 2;// to place of 1. return s[ top ].ret = 2;// to place of 1. return goto l1;// recurse goto l1;// recurse l2: top++;// ready to recurse again s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].ret = 3;// to place of 2nd return s[ top ].ret = 3;// to place of 2nd return goto l1;// recurse goto l1;// recurse l3:// two returns simulated top -= 2;// simulate 2 returns top -= 2;// simulate 2 returns s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; } //end if } //end if if ( 2 == s[ top ].ret ) {// second recursive call if ( 2 == s[ top ].ret ) {// second recursive call goto l2; goto l2; }else if ( 3 == s[ top ].ret ) { }else if ( 3 == s[ top ].ret ) { goto l3; goto l3; } //end if } //end if l4:return s[ top ].fibo;// all done } //end nr_fibo

19 19 Simulate Recursion, fibo() Results r_fibo( 0) = 0, calls = 1 r_fibo( 1) = 1, calls = 1 r_fibo( 2) = 1, calls = 3 r_fibo( 0) = 0, calls = 1 r_fibo( 1) = 1, calls = 1 r_fibo( 2) = 1, calls = 3 r_fibo( 3) = 2, calls = 5 r_fibo( 4) = 3, calls = 9... r_fibo( 3) = 2, calls = 5 r_fibo( 4) = 3, calls = 9... r_fibo(22) = 17711, calls = 57313 r_fibo(23) = 28657, calls = 92735 r_fibo(24) = 46368, calls = 150049 r_fibo(25) = 75025, calls = 242785 r_fibo(26) = 121393, calls = 392835 r_fibo(27) = 196418, calls = 635621 r_fibo(28) = 317811, calls = 1028457 r_fibo(29) = 514229, calls = 1664079 nr_fibo( 0) = 0 nr_fibo( 1) = 1 nr_fibo( 2) = 1 nr_fibo( 3) = 2 nr_fibo( 4) = 3... r_fibo(22) = 17711, calls = 57313 r_fibo(23) = 28657, calls = 92735 r_fibo(24) = 46368, calls = 150049 r_fibo(25) = 75025, calls = 242785 r_fibo(26) = 121393, calls = 392835 r_fibo(27) = 196418, calls = 635621 r_fibo(28) = 317811, calls = 1028457 r_fibo(29) = 514229, calls = 1664079 nr_fibo( 0) = 0 nr_fibo( 1) = 1 nr_fibo( 2) = 1 nr_fibo( 3) = 2 nr_fibo( 4) = 3... nr_fibo(22) = 17711 nr_fibo(23) = 28657 nr_fibo(24) = 46368 nr_fibo(25) = 75025 nr_fibo(26) = 121393 nr_fibo(27) = 196418 nr_fibo(28) = 317811 nr_fibo(29) = 514229

20 20 Simulating Return of fibo() Must the computation of the continuation place be after the if-statement? Or can we relocate it into the Else-Clause? Must the computation of the continuation place be after the if-statement? Or can we relocate it into the Else-Clause? That would lead to a partial simulation, in which only the case arg > 1 continues correctly That would lead to a partial simulation, in which only the case arg > 1 continues correctly Yet even cases for arg <= 1 must compute the right continuation via (unstructured) brute-force gotos: Yet even cases for arg <= 1 must compute the right continuation via (unstructured) brute-force gotos: if ( 2 == s[ top ].ret ) {// second recursive call goto l2; goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; goto l3; } //end if

21 21 Simulate Recursion, fibo2() unsigned nr_fibo2( unsigned arg ) { //nr_fibo2 struct_s_tp s[ MAX_STACK ];// stack can be local struct_s_tp s[ MAX_STACK ];// stack can be local unsigned top = 0;// initially unsigned top = 0;// initially s[ top ].arg = arg;// copy arg to stack s[ top ].arg = arg;// copy arg to stack s[ top ].ret = 4;// if all fails, return s[ top ].ret = 4;// if all fails, return l1: if ( s[ top ].arg <= 1 ) { s[ top ].fibo = s[ top ].arg; s[ top ].fibo = s[ top ].arg; if ( 2 == s[ top ].ret ) {// second recursive call if ( 2 == s[ top ].ret ) {// second recursive call goto l2; goto l2; }else if ( 3 == s[ top ].ret ) { }else if ( 3 == s[ top ].ret ) { goto l3; goto l3; } //end if } //end if }else{ }else{ top++;// ready to recurse top++;// ready to recurse s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].ret = 2;// to place of 1. return s[ top ].ret = 2;// to place of 1. return goto l1;// recurse goto l1;// recurse l2: top++;// ready to recurse again s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].ret = 3;// to place of 2nd return s[ top ].ret = 3;// to place of 2nd return goto l1;// recurse goto l1;// recurse l3:// two returns simulated top -= 2;// simulate 2 returns top -= 2;// simulate 2 returns s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; if ( 2 == s[ top ].ret ) {// second recursive call if ( 2 == s[ top ].ret ) {// second recursive call goto l2; goto l2; }else if ( 3 == s[ top ].ret ) { }else if ( 3 == s[ top ].ret ) { goto l3; goto l3; } //end if } //end if l4: return s[ top ].fibo;// all done } //end nr_fibo2

22 22 Simulate Recursion, hanoi() void nr_hanoi( unsigned discs, char* start, char* buffer, char* target ) { //nr_hanoi struct_h_type s[ MAX_STACK ]; unsigned top = 0; s[ top ].discs = discs; s[ top ].start = start; s[ top ].buffer = buffer; s[ top ].target = target; s[ top ].ret = 4; l1:if ( s[ top ].discs > 0 ) { top++; s[ top ].discs = s[ top-1 ].discs - 1; s[ top ].start = s[ top-1 ].start; s[ top ].buffer= s[ top-1 ].target; s[ top ].target= s[ top-1 ].buffer; s[ top ].ret = 2; goto l1; l2:printf( "nr move disc %1u from %s to %s\n", s[ top ].discs, s[ top ].start, s[ top ].target ); top++; top++; s[ top ].discs = s[ top-1 ].discs - 1; s[ top ].discs = s[ top-1 ].discs - 1; s[ top ].start = s[ top-1 ].buffer; s[ top ].start = s[ top-1 ].buffer; s[ top ].buffer= s[ top-1 ].start; s[ top ].buffer= s[ top-1 ].start; s[ top ].target= s[ top-1 ].target; s[ top ].target= s[ top-1 ].target; s[ top ].ret = 3; s[ top ].ret = 3; goto l1; goto l1; } //end if l3:if ( 2 == s[ top ].ret ) { top--; top--; goto l2; goto l2; }else if ( 3 == s[ top ].ret ) { top--; top--; goto l3; goto l3; } //end if } //end nr_hanoi

23 23 References  Douglas R. Hofstader, “Gödel, Escher, Bach: an eternal golden braid”, Basic Books, 1999, ISBN 0465026567  Ackermann functiona at NIST: http://xlinux.nist.gov/dads/HTML/ackermann.html  Herbert G Mayer: “Advanced C Programming on the IBM PC”, 1989, Windcrest, ISBN 0-8306-9163-4  Non-recursive solution to Towers of Hanoi: http://portal.acm.org/citation.cfm?id=948602


Download ppt "1 CS 410 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS status 7/3/2011."

Similar presentations


Ads by Google