CMPT 128: Introduction to Computing Science for Engineering Students Recursion © Janice Regan, CMPT 128, February. 2007 0 Introduction to Recursion Recursion: A function that calls itself is said to be a recursive function A function that calls itself results in direct recursion If function A calls function B, and in turn function B calls function A this results in indirect recursion C++, like most high-level languages, allows recursion Recursion can result in elegant easy to understand and develop code (‘efficient’ development and maintenance) Recursion may be less ‘efficient’ to execute than an equivalent iterative solution, © Janice Regan, CMPT 128,2007-2013 1 Recursive void Functions Recursion is based on the design technique know as ‘divide and conquer’ Divide: break the task into smaller parts Conquer: solve a large and complex problem efficiently When solving a problem recursively Divide: each smaller part of the task is the same as the whole task The same algorithm or series of steps is used for the whole problem and for each smaller task the problem is divided into © Janice Regan, CMPT 128,2007-2013 2 Example: quick sort Consider large task: sort a large array into descending order Choose pivot from ‘middle’ of array, Switch the pivot and first element in array Sort the array into two parts, one with all numbers larger than the pivot, one with all numbers smaller than the pivot Move the pivot back between the two parts Subtasks must be smaller versions of original task Subtask 1: repeat the main task on the part of the array with elements larger than the pivot Subtask 2: repeat the main task on the part of the array with elements smaller than the pivot. Quick sort is a method of the type that is well suited to be solved by recursion © Janice Regan, CMPT 128,2007-2013 3 Recursive void Function: a simple example Task: calculate the sum of the squares of all integers from 1 to j, for all integers j from 1 to n. Display sums of integers vertically, one line for each j, beginning with j=1 Example call: sumSqIntegers(4); Produces output: 1 5 14 30 © Janice Regan, CMPT 128,2007-2013 4 Sums of squares: Recursive Definition Break problem into two cases Base case: if n=1 Simply write number n to screen (1*1=1) Do not call the function again Return to the calling function (previous recursive call) Recursive case: if n>1, two subtasks: Calculate and print all sums for j=1,..,n-1 Calculate and print the sum for j=n Return to the calling function © Janice Regan, CMPT 128,2007-2013 5 Sums of squares: Recursive Definition Example: argument 4: 1st subtask calculates the sums for each j j=1,…,3 and prints them one per line 1, 5, 14 Call the function sumSqIntegers for j-1 2nd subtask calculates the sum for j=4 and prints it on a new line, 30 Return to the calling program © Janice Regan, CMPT 128,2007-2013 6 sumSqIntegers: Function Definition void sumSqIntegers(int maxInteger, int& value) { if (maxInteger == 1) { cout << maxInteger << endl; //Base case value = maxInteger; } else { sumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; //Recursive case cout << value << endl; } } © Janice Regan, CMPT 128,2007-2013 7 Sums of squares: Trace (1) Example call: sumSqIntegers(4,value); sumSqIntegers(3,value); sumSqIntegers(2,value); sumSqIntegers(1,value); cout << 1 << endl; cout << 2*2 + 1 << endl; cout << 3*3 + 5 << endl; cout << 4*4 +14 << endl; © Janice Regan, CMPT 128,2007-2013 //1st call //2nd call //3rd call //4th call //4th call //3rd call //2nd call //1st call 8 Sums of squares: Trace (2) During 1st call n=4, and the function is called for n=3 (recursive case) During 2nd call n=3, and the function is called for n=2 (recursive case) During 3rd call n=2, and the function is called for n=1 (recursive case) During 4th call n=1, 1is printed to the screen and the cursor moves to the next line (stopping case) then returns to 3rd case During the remainder of the 3rd call n=2, 5 is printed to the screen and the cursor moves to the next line (completes recursive case) then returns to 2nd case During the remainder of the 2nd call n=3, 14 is printed to the screen and the cursor moves to the next line (completes recursive case) then returns to 1st case During the remainder of the 1st call n=4, 30 is printed to the screen and the cursor moves to the next line (completes recursive case) then returns to calling program © Janice Regan, CMPT 128,2007-2013 9 Sums of squares: Trace (3) Calling function: sumSqIntegers(4, value) void sumSqIntegers(int maxInteger, int& value) { if (maxInteger == 1) { 1st call: sumSqIntegers(3, value) //Base case cout << maxInteger << endl; value = maxInteger; 2nd call: sumSqIntegers(2, value) } else { //Recursive case sumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; cout << value << endl; } 3rd call: sumSqIntegers(1, value) 4th call: Print 1, value=1 © Janice Regan, CMPT 128,2007-2013 } 10 Sums of squares: Trace (4) Calling function: sumSqIntegers(4, value) void sumSqIntegers(int maxInteger, int& value) { if (maxInteger == 1) { 1st call: sumSqIntegers(3, value) //Base case cout << maxInteger << endl; value = maxInteger; } else { //Recursive case sumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; cout << value << endl; } 2nd call: sumSqIntegers(2, value) 3rd call: determine value value = 1+2*2 Print 5 } © Janice Regan, CMPT 128,2007-2013 11 Sums of squares: Trace (5) Calling function: sumSqIntegers(4, value) void sumSqIntegers(int maxInteger, int& value) { if (maxInteger == 1) { 1st call: sumSqIntegers(3, value) //Base case cout << maxInteger << endl; value = maxInteger; } else { //Recursive case sumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; cout << value << endl; } 2nd call: determine value value = 5 + 3*3 Print 14 } © Janice Regan, CMPT 128,2007-2013 12 Sums of squares: Trace (6) Calling function: sumSqIntegers(4, value) void sumSqIntegers(int maxInteger, int& value) { if (maxInteger == 1) { 1st call: determine value value = 14+4*4 Print 30 //Base case cout << maxInteger << endl; value = maxInteger; } else { //Recursive case sumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; cout << value << endl; } } © Janice Regan, CMPT 128,2007-2013 13 Infinite Recursion Base case MUST eventually be entered If there is no base case OR If the base case is never reached Then recursion continues for ever (or at least until memory for new function frames is exhausted) In our example (sumSqIntegers): Base case happened when we reached 1, the sum of the squares of integers 1,..,1 is 1 That’s when recursion stopped and we began returning up through the stack of recursive frames © Janice Regan, CMPT 128,2007-2013 14 Infinite Recursion: Example Consider alternate function definition: void altSumSqIntegers(int maxInteger, int& value) { altsumSqIntegers(maxInteger-1, value); value += maxInteger * maxInteger; cout << value << endl; } Seems logical on the surface but there is no stopping case so recursion never stops © Janice Regan, CMPT 128,2007-2013 15 Recursion: Review A recursive function must have: One or more recursive cases where the function accomplishes it’s task by: Making one or more recursive calls to solve smaller versions of original task One or more base cases or stopping cases where the function accomplishes it’s task by simple calculation without recursive call © Janice Regan, CMPT 128,2007-2013 16 Stacks for Recursion A stack Specialized memory structure Like stack of paper Place each new page (frame) on top When you finish with one page (frame) then take the page (frame) from the top of the stack and continue Called "last-in/first-out“ (LIFO) memory structure Recursion uses stacks Each recursive call placed on stack When one completes, last call is removed from stack © Janice Regan, CMPT 128,2007-2013 17 Stack Overflow Size of stack limited Memory is finite Long chain of recursive calls continually adds to stack All are added before base case causes removals If stack attempts to grow beyond limit: Stack overflow error Infinite recursion always causes this © Janice Regan, CMPT 128,2007-2013 18 Why Use Recursion Recursion is not always the ‘best solution’ Some languages do not allow recursion Any task accomplished with recursion can also be done using iteration Recursive: Recursion can result in elegant easy to understand and develop code (‘efficient’ development and maintenance) Recursion may be less ‘efficient’ to execute than an equivalent iterative solution (may run slower) © Janice Regan, CMPT 128,2007-2013 19 Recursive Functions can have any type Recursion not limited to void functions Can return value of any type Same approach as for our original example Let’s recast that original example so that the recursive function has type int rather than type void © Janice Regan, CMPT 128,2007-2013 20 sumSqIntegers: Function Definition int sumSqIntegers(int maxInteger) { int value; if (maxInteger == 1) { cout << maxInteger << endl; return(maxInteger); } else { sumSqIntegers(maxInteger-1); value += maxInteger * maxInteger; cout << value << endl; return(value); } } © Janice Regan, CMPT 128,2007-2013 //Base case //Recursive case 21 Sums of squares: Trace (1) Example call: sumSqIntegers(4); sumSqIntegers(3); sumSqIntegers(2); sumSqIntegers(1); cout << 1 << endl; cout << 2*2 + 1 << endl; cout << 3*3 + 5 << endl; cout << 4*4 +14 << endl; © Janice Regan, CMPT 128,2007-2013 //1st call //2nd call //3rd call //4th call //4th call //3rd call //2nd call //1st call 22 Return a Value Recursion Example: Powers Recall predefined function pow(): result = pow(2.0,3.0); Returns 2 raised to power 3 (8.0) Takes two double arguments Returns double value Let’s write recursively For simple example © Janice Regan, CMPT 128,2007-2013 23 Function Definition for power() int power(int x, int n) { if (n<0) { cout << "Illegal argument"; exit(1); } if (n>0) { return (power(x, n-1)*x); } else { return (1); } } © Janice Regan, CMPT 128,2007-2013 24 Calling Function power() Larger example: power(2,3); power(2,2)*2 power(2,1)*2 power(2,0)*2 1 Reaches base case Recursion stops Values "returned back" up stack © Janice Regan, CMPT 128,2007-2013 25 Recursive Design Techniques Start simple Make sure the problem can be broken into parts that are copies of each other and the ‘whole’ problem Define your recursive cases Define your stopping case or cases Do the recursive cases return or produce the correct results When will the series of recursive calls end Do the stopping cases return or produce the correct results Make sure that you stop your recursive case and enter the stopping cases when conditions are correct © Janice Regan, CMPT 128,2007-2013 26 Recursive design: power Consider power() again Can the problem be broken down into smaller parts: xn = xi*xk where n=i+k this problem can be broken down into parts that are equivalent to the ‘whole’ problem Design the recursive case power(x, n) returns: power(x, n – 1) * x Is this correct? xn = xn-1*x so yes Design the stopping case x0=1, x1=x When do we stop? When n=0 we know the answer regardless of the value of x So out stopping case is n=0, x0=1 © Janice Regan, CMPT 128,2007-2013 27 Recursive Design Check: power() Can infinite recursion happen with the power() function we have designed 2nd argument decreases by 1 each call Eventually we will reach the base case of n=0 Does the stopping case return the correct value: n=0 is base case which implies power(x,0) power(x,0) returns 1, which is correct for x0 Does the recursive case return the correct value: For n>1, power(x,n) returns power(x,n-1)*x xn = xn-1*x so this is correct © Janice Regan, CMPT 128,2007-2013 28 Another recursive example Now lets return to our discussion of quick sort and see the application of recursion to that algorithm © Janice Regan, CMPT 128,2007-2013 29