© Janice Regan, CMPT 102, Sept CMPT 102 Introduction to Scientific Computer Programming Recursion
© Janice Regan, CMPT 102, Sept Introduction to Recursion A function that "calls itself" Said to be recursive In function definition, call to same function C allows recursion As do most high-level languages Can be useful programming technique Has limitations
© Janice Regan, CMPT 102, Sept Functions and Recursion Divide and Conquer Well known design method Task is broken into smaller subtasks Subtasks can be independent parts of the larger task Subtasks can be copies of the same task Use recursion when the subtasks are smaller versions of the same task!
© Janice Regan, CMPT 102, Sept Recursive void Function As and example consider the following task: Print a triangle built of N rows of *s, Row zero will hold 1 *, Row N will have 2N+1 *s N=5N=3 * *** ***** ******* ********* * *** *****
© Janice Regan, CMPT 102, Sept Recursive problem Consider task: Print a triangle built of N rows of *s Subtask 1: print first (N-1) rows of *s Subtask 2: print Nth row of *s (2N+1 *s) Subtasks are smaller versions of original task! Each task involves printing a row (or rows) or *s Therefore, the problem is of a type that can be usefully solved using a recursive method Can use this observation to build an ‘elegant’ solution
© Janice Regan, CMPT 102, Sept Print triangle: Recursive Design Break problem into two cases Simple/base case: if n=0 print one * to the screen Recursive case: if n>0, two subtasks: 1- print the first n-1 rows of the triangle 2- print the nth row of the triangle (2N+1 *s) Example: argument 4: 1 st subtask prints 3 rows of *s (as show on slide 3) 2 nd subtask displays 4
© Janice Regan, CMPT 102, Sept printTriangle function void printTriangle(int N) { if( N <= 0) { return } if (N == 1) //Base case {printf(“*\n”);} else {//Recursive step printTriangle(N-1); for(i=0; i<N; i++) { printf(“**”); } printf(“*\n”); } }
© Janice Regan, CMPT 102, Sept printTriangle: execution Example calling program calls printTriangle(3); (which calls) printTriangle(2); (which calls) printTriangle(1); Which prints a row with 1 * and a newline then returns Which prints a row with 3 *s and a newline then returns Which prints a row with 5 *s and a newline then returns Notice 1 st two calls call printTriangle recursively
© Janice Regan, CMPT 102, Sept Recursion—Details (1) Each time a function is called The execution of the calling function stops to wait for the results of the called function to be returned A new frame (area in memory) is assigned for the called function, arguments are copied in, and the called function begins executing. At some point (points) in the execution of the called function, the called function will call itself The execution of the called function stops to wait for the results of the recursive call to itself.
© Janice Regan, CMPT 102, Sept Recursion—Details (2) A new frame is assigned for the recursive call, arguments are copied, and the recursive call to the function begins executing. The process continues with successive recursive calls (repetition of steps 3-5), until one of the recursive calls executes the stopping case inside the recursive function. The stopping case does not call the recursive function again but instead returns the answer for the stopping case to its calling function
© Janice Regan, CMPT 102, Sept Recursion—Details (3) The calling function takes the returned value and completes its own execution returning a value to its own calling function Step 7 is repeated until the final answer is returned to the original calling function
© Janice Regan, CMPT 102, Sept Recursion Big Picture Outline of successful recursive function: One or more cases where function accomplishes it’s task by making one or more recursive calls to solve smaller versions of the original task Called recursive cases One or more cases where function accomplishes it’s task without recursive calls Called base cases or stopping cases
© Janice Regan, CMPT 102, Sept Infinite Recursion Base case MUST eventually be entered If base case is not reached (an error in design or implementation has occurred) then the recursion will consider forever Recursive calls will never end! Remember the printTriangle example: Base case: print a single * and return Recursive calling stopped when base case executed
© Janice Regan, CMPT 102, Sept Infinite Recursion Example Consider alternate function definition: void printTriangleOOPS(int n) { printTriangleOOPS(N-1); for(i=0; i<N; i++) { printf(“**”); } printf(“*\n”); } } Seems reasonable at first glance: BUT There is not base case Recursion never stops: printTriangle keeps calling itself for ever The triangle is never printed (never get to the for loop)
© Janice Regan, CMPT 102, Sept What is a Stack? A stack is a specialized memory structure Think of a stack as like a stack of sheets of paper. You always place a new sheet of paper on top of the stack, when you need a piece of paper from the stack, you take it from the top of the stack. Called "last-in/first-out" memory structure
© Janice Regan, CMPT 102, Sept Recursion and Stacks (1) Recursion uses stacks. Consider a recursive function (example: printTriangle(M) ). Consider The first call to printTriangle (made from the calling program) will be called printTriangle0 and has argument M When this call to printTriangle (printTriangle0) is executed a frame is created in which printTriangle0 executes. printTriangle0 executes in this frame until it calls printTriangle, then it stops and waits for the value to be returned from the recursive call to printTriangle (printTriangle1) The frame for printTriangle0 is placed on the stack of frames
© Janice Regan, CMPT 102, Sept Recursion and Stacks (2) The second call to printTriangle, the first recursive call, (made in printTriangle0) will be printTriangle1 and has argument M-1 When this call to printTriangle (printTriangle1) is executed a frame is created in which printTriangle1 executes. printTriangle1 executes in this frame until it calls itself, then it stops and waits for the value to be returned from the recursive call to printTriangle (printTriangle2) The frame for printTriangle1 is placed on the stack of frames
© Janice Regan, CMPT 102, Sept Recursion and Stacks (3) The third call to printTriangle (made from printTriangle1) will be printTriangle2 and has argument M-2 When this call to printTriangle (printTriangle2) is executed a frame is created in which printTriangle2 executes. printTriangle2 executes in this frame until it calls itself, then it stops and waits for the value to be returned from the recursive call to printTriangle (printTriangle3) The frame for printTriangle2 is placed on the stack of frames
© Janice Regan, CMPT 102, Sept Recursion and Stacks (4) This pattern continues until the M-1st call to printTriangle (made from printTriangle(M-2)) called printTriangle(M-1) and has argument M-M+1=1 When this call to printTriangle (printTriangleM-1) is executed a frame is created in whiich printTriangleM-1 executes printTriangleM-1 executes in this frame, since this is the stopping case for printTriangle, a single * is printed and the function terminates returning control to the function which called it.
© Janice Regan, CMPT 102, Sept Recursion and Stacks (5) The previous frame is taken off the top of the stack. The loop following the recursive call is executed and prints 3 *s. The frame is then destoyed returning control to the function which called it The previous frame is taken off the top of the stack. The loop following the recursive call is executed and prints 5 *s. The frame is then destoyed returning control to the function which called it This pattern continues until all frames in the stack have been executed and the triangle has been completely printed
© Janice Regan, CMPT 102, Sept Stack Overflow Size of stack limited Memory is finite, too many frames in the stack will cause memory to be exhausted Each recursive call adds a frame to the stack so a limited number of recursive calls can be supported by a stack of a particular size. Infinite recursion always causes the stack to overflow (memory to be exhausted) Too many levels of recursion (not infinite) can also cause the stack to overflow.
© Janice Regan, CMPT 102, Sept Recursion Vs Iteration Recursion not always an optimal solution Not all languages permit recursion Any algorithm implemented using recursion can also be implemented iteratively, using loops Recursive implementations : Runs slower, uses more storage Elegant solution; less coding, less complications, faster and more straightforward to implement
© Janice Regan, CMPT 102, Sept Recursive Functions with Return Values Recursion not limited to void functions as in our example of printTriangle Can have a recursive function with a return value of any type Use same technique as for void functions 1. One or more cases iterative cases Should be smaller cases of the entire problem 2. One or more base cases
© Janice Regan, CMPT 102, Sept Powers For example, consider C library function pow(): twoCubed = pow(2.0,3.0); Returns 2 raised to power 3 (8.0) Takes two double arguments Returns double value Let’s write our own power() function Our power() function will be implemented as a recursive function int power(int a, int b);
© Janice Regan, CMPT 102, Sept Definition for power() int power(int x, int n) { if (n DBL_EPSILON) {return (power(x, n-1)*x);} else {return (1);} }
© Janice Regan, CMPT 102, Sept Calling Function power() Example calls: power(2, 0); executes stopping case: return 1; power(2, 1); executes iterative case: return (power(2, 0) * 2); this requires evaluating power(2, 0) evaluating power(2,0) evaluates stopping case returns 1; Now complete evaluation of iterative case return 1*2
© Janice Regan, CMPT 102, Sept Calling Function power() Another example: power(2,3); iterative case: power(2,2)*2 iterative case: power(2,1)*2 iterative case: power(2,0)*2 base case: 1 return 1*2 return 2*2 return 4*2 Recursion stops at base case Values "returned back" up stack
© Janice Regan, CMPT 102, Sept Thinking Recursively: factorial Consider n! = 1 * 2 * … * n Recursive definition of factorial: int fact(int n) Define recursive case Given the value of (n-1)!, what is the value of n! n! = n* (n-1)! Define stopping case (or cases) 0! =1 1! =1 Factorial of a negative number is not defined so 0 is the smallest number that needs to be considered
© Janice Regan, CMPT 102, Sept Recursive Design Process Don’t trace entire recursive sequence! Just check 3 properties: 1. No infinite recursion 2. Stopping cases return correct values 3. Recursive cases return correct values
© Janice Regan, CMPT 102, Sept Recursive Design Check: fact() Check fact() against 3 properties: 1. No infinite recursion: argument decreases by 1 each call Eventually must get to base case of 1 2. Stopping case returns correct value: fact(0) = fact(1) = 1 is base case Returns 1, which is correct for 0! or 1! 3. Recursive calls correct: For n>1, fact(n) returns fact(n-1)*n Plug in sample value to verify
© Janice Regan, CMPT 102, Sept fact() int fact( ) { if (n < 0 ) { return -999; } /* indicates incorrect input */ if (n == 0 || n == 1) { return 1; /* stopping case */ } else { return ( fact(n-1) * n ); /* iterative case */ }
© Janice Regan, CMPT 102, Sept Recursion: Quick sort Recall our discussion of sorting, (in the unit on arrays) Quicksort is a recursive algorithm Each time the Quicksort function is called the array to be sorted is divided into two parts, the left part for which all values are less than the pivot value the right part for which all values are larger than the pivot value the Quicksort function is then recursively called First to sort the right part of the array Again to sort the left part of the array
© Janice Regan, CMPT 102, Sept Quicksort function: void Quicksort( int InArray[], int loBound, int hiBound ) { /* block of code to break array into two parts */ /* the first part of the array hold all values < pivot */ /* the second part holds all values > pivot */ Quicksort(InArray, loBound, hiSwap-1); /* left part */ Quicksort(InArray, hiSwap+1, hiBound); /* right part */ }
© Janice Regan, CMPT 102, Sept Unsorted Array passed into first call to Quicksort (call 0) The array is partitioned into left part (all values pivot) Within call 0 Quicksort is called for the left part then for the right part Sample Quick Sort: 1 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Pivot, middle element Left part Right part
© Janice Regan, CMPT 102, Sept When Quicksort is called for the left part, the execution of the present function (call 0 ) stops, the call0 function frame is placed on the stack, (call 0 sorts A[0]-A[12] ) A new function frame to execute Quicksort on the left part of the array is created. Execution of call 1 begins (call 1 sorts left part, A[0]- A[8]). Call 1 partitions Left part into two sections, left part2 (all values pivot of left part) Within call 1 Quicksort is called for the left part2 then for the right part2 Sample Quick Sort: 2 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part2 Left part2
© Janice Regan, CMPT 102, Sept When Quicksort is called for the left part2, the execution of the present function (call 1 ) stops, the call 1 function frame is placed on the stack. (call 1 sorts A[0]-A[8] ) A new function frame to execute Quicksort on the left part2 of the array is created. Execution of call 2 begins (call 2 sorts left part2, A[0]-A[1]). Since there are only two elements in left part2 this is a stopping case. The elements are put in order and Quicksort returns. The frame created for call2 is destroyed and the last frame added to the stack is reactivated (call 1) Sample Quick Sort: 3 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part2
© Janice Regan, CMPT 102, Sept When Quicksort returns from sorting left part2, the execution of call 1 is reinitiated. The call 1 function frame is retrieved from the stack (call 1 sorts A[0]-A[8] ). Then call 1 calls Quicksort for right part2, the execution of call 1 stops, the call1 function frame is placed back on the stack A new function frame to execute Quicksort on the right part2 of the array is created. Execution of call 3 begins (call 3 sorts right part2, A[3]-A[8]). Call 3 partitions right part2 into two sections, left part3 (all values pivot of right part2) Within call 3 Quicksort is called for the left part3 then for the right part3 Sample Quick Sort: 4 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part3 Left part3
© Janice Regan, CMPT 102, Sept When Quicksort is called for the left part3, the execution of the present function (call 3 ) stops, the call 3 function frame is placed on the stack. (call 3 sorts A[3]-A[8] ) A new function frame to execute Quicksort on the left part3 of the array is created. Execution of call 4 begins (call 4 sorts left part3, A[3]-A[3]). Since there is only one element in left part3 this is a stopping case and Quicksort returns. The frame created for call4 is destroyed and the last frame added to the stack is reactivated (call 3) Sample Quick Sort: 5 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part3
© Janice Regan, CMPT 102, Sept When Quicksort returns from sorting left part3, the execution of call 3 is reinitiated. The call 3 function frame is retrieved fro the stack (call 3 sorts A[3]-A[8] ). Then call 3 calls Quicksort for right part3, the execution of call 3 stops, the call 3 function frame is placed back on the stack A new function frame to execute Quicksort on the right part3 of the array is created. Execution of call 5 begins (call 5 sorts right part3, A[5]-A[8]). Call 5 partitions right part3 into two sections, left part4 (all values pivot of right part2) Within call 5 Quicksort is called for the left part4 then for the right part4 Sample Quick Sort: 6 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part4 Left part4
© Janice Regan, CMPT 102, Sept When Quicksort is called for the left part4, the execution of the present function (call 5 ) stops, the call 5 function frame is placed on the stack. (call 5 sorts A[5]-A[8] ) A new function frame to execute Quicksort on the left part4 of the array is created. Execution of call 6 begins (call 6 sorts left part4, A[5]-A[6]). Since there are only two elements in left part3 this is a stopping case and Quicksort returns. The frame created for call 6 is destroyed and the last frame added to the stack is reactivated (call 5) Sample Quick Sort: 7 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part Right part4
© Janice Regan, CMPT 102, Sept When Quicksort returns from sorting left part4, the execution of call 5 is reinitiated. The call 5 function frame is retrieved from the stack (call 5 sorts A[5]-A[8] ). Then call 5 calls Quicksort for right part4, the execution of call 5 stops, the call 5 function frame is placed back on the stack A new function frame to execute Quicksort on the right part4 of the array is created. Execution of call 7 begins (call 7 sorts right part4, A[8]-A[8]). Since there is only one element in right part4 this is a stopping case and Quicksort returns. The frame created for call 7 is destroyed and the last frame added to the stack is reactivated (call 5) Sample Quick Sort: 8 A[0]A[1]A{2]A[3]A[4]A[5}A[6]A[7]A[8]A[9]A[11]A[10]A[12] Right part
© Janice Regan, CMPT 102, Sept When Quicksort returns from sorting right part4, the execution of call 5 is reinitiated. The call 5 function frame is retrieved from the stack (call 5 sorts A[5]-A[8] ). Call 5 has now completed both its recursive calls. The frame created for call 5 is destroyed and the last frame added to the stack is reactivated (call 3 sorts A[3]-A[8]) Call 3 has now completed both its recursive calls The frame created for call 3 is destroyed and the last frame added to the stack is reactivated (call 1 sorts A[0]-A[8] ). Then call 1 calls Quicksort for right part, the execution of call 1 stops, the call 1 function frame is placed back on the stack A new function frame to execute Quicksort on the right part of the array is created. Execution of call 8 begins (call 8 sorts right part, A[10]-A[12]). Call 8 partitions right part into two sections, left part5(all values pivot of right part) Array is now in order Sample Quick Sort: 9