420 likes | 526 Views
Recursion. Joe Meehean. Recursive Methods. void g(){ ... h(); ... } void h(){ ... g(); ... }. void f(){ ... f(); ... }. call themselves directly or indirectly. Recursive Method Example. int factorial( int x){ int handback ; if( x <= 1 ){ handback = 1;
E N D
Recursion Joe Meehean
Recursive Methods void g(){ ... h(); ... } void h(){ ... g(); ... } void f(){ ... f(); ... } • call themselves directly • or indirectly
Recursive Method Example int factorial(int x){ inthandback; if( x <= 1 ){ handback = 1; }else{ handback = x * factorial(x-1); } return handback; } factorial(4) = 4 * factorial(4 – 1) factorial(3) = 3 * factorial(3 – 1) … factorial(1) = 1
Conceptual Recursion • When a recursive call is made: • marks current place in code • clones itself • copy of code • new parameters • new set of uninitialized local variables • clone executes starting at beginning • When clone returns • clone is cleaned up • previous version starts at just afterrecursive method call
CLASS ACTIVITY void doClap(int k){ if(k == 0 ) return; clap k times; doClap(k – 1); return; } 1st person: doClap(3)
Incorrect Recursion void doClap(int k){ clap k times; doClap(k + 1); return; } • doClap(3) • 3, 4, 5, 6, … • memory access error • need code to stop recursion eventually
Recursion Rule 1 void doClap(int k){ if( k == 0 ) return; clap k times; doClap(k + 1); return; } • Still has infinite recursion? • Must have a base case • Condition under which no recursive call is made • Helps prevent infinite recursion
Recursion Rule 2 void doClap(int k){ if( k == 0 ) return; clap k times; doClap(k - 1); return; } • Does doClap follow rules 1 & 2? • Infinite recursion? • If so, how should we change the code Must make progress towards base case
Recursion Rule 2 void doClap(int k){ if( k <= 0 ) return; clap k times; doClap(k - 1); return; } • Does doClap follow rules 1 & 2? • Infinite recursion? • If so, how should we change the code Must make progress towards base case
Another Example void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k]; printStr(s, k+1); } printStr(“hello”, 0) => “hello” printStr(“hello”, 2) => “llo”
Recursion Rule 3 void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k]; printStr(s, k+1); } • Design rule • assume that all recursive calls work • even if you haven’t finished writing it yet
Another Example void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k] << endl; printStr(s, k+1); } printStr(“hello”, 0) h printStr(“hello”, 1) e printStr(“hello”, 2) l printStr(“hello”, 3) l printStr(“hello”, 4) o printStr(“hello”, 5)
Another Example void printStr(string s, int k){ if( k >= s.length() ) return; printStr(s, k+1); cout << s[k] << endl; } printStr(“hello”, 0) h printStr(“hello”, 1) e printStr(“hello”, 2) l printStr(“hello”, 3) l printStr(“hello”, 4) o printStr(“hello”, 5)
Understanding Recursion • A method may have statements • before recursive call • after recursive • both • After statements are done only • when the recursive call is finished • and all the recursive calls recursive calls have finished
DISCUSSION BREAK!!! void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k] << endl; printStr(s, k+1); cout << s[k] << endl; } Whats printed for printStr(“STOP, 0)?
DISCUSSION BREAK!!! void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k] << endl; printStr(s, k+1); cout << s[k] << endl; } • Whats printed for printStr(“STOP, 0)? • STOPPOTS • How can we change it to print • STOP<BREAK>POTS
DISCUSSION BREAK!!! void printStr(string s, int k){ if( k >= s.length() ){ cout << “<BREAK>” << endl; return; } cout << s[k] << endl; printStr(s, k+1); cout << s[k] << endl; } • How can we change it to print • STOP<BREAK>POTS
Local Variables void mystery(int j){ if( j <= 0 ) return; intk = j * 2; mystery(j – 1); cout << k << “ “; } • Each clone gets own local variables • Outputs => 2 4 6
Recursive Call in Expressions // sums #s from 1 to max // Iterative version intdoSum(int max){ int sum = 0; for(inti = 1; i <= max; i++){ sum += i; } return sum; }
Recursive Call in Expressions // sums #s from 1 to max // Iterative version intdoSum(int max){ int sum = 0; for(inti = 1; i <= max; i++){ sum += i; } return sum; } // Recursive version intdoSum(int max){ if( max <= 0 ) return 0; return max + doSum(max – 1); }
Recursive Call in Expressions doSum(3) + 3 doSum(2) + 2 doSum(1) + 1 doSum(0)
Recursion in Practice • Can do recursion with int parameters • count up or down to base case • Or, with recursive data structures • a linked list is either empty • or a node followed by a linked list
Recursion in Practice void printList(Node<T> *node){ if( node == NULL ) return; cout << node->data << endl; printList(node->next); } Prints list in order
DISCUSSION BREAK!!! void printList(Node<T> *node){ if( node == NULL ) return; cout<< node->data << endl; printList(node->next); } How can we change this code to print list backwards?
DISCUSSION BREAK!!! void printList(Node<T> *node){ if( node == NULL ) return; printList(node->next); cout << node->data << endl; } Prints list backwards Rare case where recursion makes code faster as well as simpler
How does recursion really work? • Activation records (ARs) • stack of ARs maintained at runtime • 1 AR for each active method • active methods have been called, but not returned yet • Each AR includes: • function parameters • local variables • return address
How does recursion really work? • Activation Record Stack • when method is called, its AR is pushed • when method returns, its AR is popped • return address in popped AR tells program where to return control flow
Recursion Examples • Auxiliary recursion • wrapper method to do something ONCE before or after recursion • OR pass more parameters • recursion in auxiliary method • wrapper sometimes called a driver • Multiple recursive calls per method • need to recurse down a few different paths • more on this when we talks about trees
Example 1 // prints a header and the endline // calls aux to print the list void printList(Node<T> *node){ cout << “The list contains: “; printAux(node); cout << endl; } // prints a single string containing // each element in brackets void printAux(Node<T>* node){ if( node == NULL ) return; cout << “[“ << node->data << “]”; printAux(node->next); }
Example 2 // public method calls private method // with member variable int List::numNodes(){ return numAux(phead_); } // actually counts the nodes int List::numAux(Node<T>* node){ if( node == NULL ){ return 0; } return 1 + numAux(node->next); }
Example 3: Extra parameters intweirdSum(vector<int>& intVect){ return sumVector(intVect, 1); } // actually sums the entries intsumVector( vector<int>& intVect, intpos){ if( pos >= intVect.size() ) return 0; return intVect[pos] + sumAux(intVect, pos + 1); } Sum all elements in a vector but the first value
Example 4: Multi-recursive calls int fib(int n){ if( n <= 2) return 1; return fib(n – 2) + fib(n – 1); } • Fibonacci • fib(1) = 1, fib(2) = 1 • fib(N) for N > 2 = fib(N-1) + fib(N-2) • fib(3) = fib(2) + fib(1) = 1 + 1 = 2
Fibonacci Call Trace fib(4) 3 + 1 2 fib(3) fib(2) 2 + 1 1 fib(1) fib(2) Note: fib(2) is called twice Redoing work
Recursion Rule 4 • Compound interest rule • Never duplicate work • don’t solve the same recursive instance in separate recursive calls • repeats work you’ve already done • more on how to prevent this in CS242
What good is it? • Recursion DOES NOT make your code faster • Recursion DOES NOT use less memory • Recursion DOES make your code simpler • sometimes
When is it a bad idea? • When you don’t get anything from it? • Tail recursion • the only recursive call is the last line • local variables stored just to be thrown away • Tail recursion can be replaced with a while loop
When is it a bad idea? void printStr(string s, int k){ if( k >= s.length() ) return; cout << s[k] << endl; printStr(s, k+1); } void printStrin(string s, int k){ while( true ){ if( k >= s.length() ) return; cout<< s[k] << endl; k = k + 1; } }
When is it a bad idea? void printString(string s, int k){ while( k < s.length()){ cout<< s[k] << endl; k = k + 1; } }
Recursion Rules Recap Base case Make progress Design rule Compound interest rule