Exploring Recursion in Programming
380 likes | 494 Vues
Learn about recursion as a programming style for proving program properties, ways to verify program correctness, imperative vs. applicative programming styles, tail recursion, execution tracing, efficient tail recursive functions, and proving program termination.
Exploring Recursion in Programming
E N D
Presentation Transcript
Data Structures and Algorithms for Information Processing Lecture 8: Recursion Lecture 8: Recursion
Recursion • We’ve seen several examples of the use of recursion • We’ll take a closer look at recursion as a style of programming • Lends itself to analytical methods; proving program properties Lecture 8: Recursion
Verifying Program Properties • How can we be sure that a program is correct? • Debug • Test cases • Make sure output matches another program • ... • None of these give absolute assurance Lecture 8: Recursion
Imperative Programming • The “usual” style in Java, using commands • Programs are written by create data (“state”) and storing it in variables • Flow of control insures that correct sequence of assignments is executed Lecture 8: Recursion
Applicative Programming • No references to other objects • No side effects (assignments, output...) • Some advantages: • Functions only return values • No need for loops • Easier to prove properties • A different programming style, and a different way to think about programming Lecture 8: Recursion
Recursion • General pattern: recursive_fn(params) { if (…) return some_value; else ... recursive_fn(new_params) ... } • A recursive function call is made somewhere in the body of the function Lecture 8: Recursion
Tail Recursion • General pattern: tail_recursive_fn(params) { if (…) return some_value; else return tail_recursive_fn(new_params) } • Tail recursive: the function does no work after the recursive call Lecture 8: Recursion
Tail Recursion • “Usual” recursive factorial // Precondition: n >= 0 static int fact1(int n) { if (n==0) return 1; else return n*fact1(n-1); } Lecture 8: Recursion
Tail Recursion • Tail recursive factorial static int fact2(int n) { // Invariant: n >= 0 return fact2_aux(n, 1); } static int fact2_aux(int n, int accum) { if (n==0) return accum; else return fact2_aux(n-1, n*accum); } Lecture 8: Recursion
Execution Trace fact1(5) 5*fact1(4) 5*4*fact1(3) 5*4*3*fact1(2) 5*4*3*2*fact1(1) 5*4*3*2*1*fact1(0) 5*4*3*2*1*1 => 120 Lecture 8: Recursion
Execution Trace fact2(5) fact2_aux(5,1) fact2_aux(4,5) fact2_aux(3,20) fact2_aux(2,60) fact2_aux(1,120) fact2_aux(0,120) => 120 Lecture 8: Recursion
Example of Non-Tail Recursion // Precondition: y > 0 static int mult (int x, int y) { if (y==1) return x; else return x + mult(x, y-1); } • Addition operation carried out after the recursive call Lecture 8: Recursion
Tail Recursion (cont) • Tail recursive functions can be more efficient • Often easier to prove properties of tail recursive functions • correctness, termination, cost • technique: induction Lecture 8: Recursion
Example: fact2 Want to prove using induction that fact2(n) => n! • We do this by proving an appropriate property of the auxiliary function: fact2_aux(n, p) => n! * p Lecture 8: Recursion
Example: fact2 (cont) • Base case: n=0 • for all p fact2_aux(0, p) => p = 0! * p • Inductive step: n > 0: • Assume true for n-1 • For all p fact2_aux(n-1, p) =>(n-1)! * p Lecture 8: Recursion
Example: fact2 (cont) • Inductive step: n > 0: • Assume true for n-1 • For all p fact2_aux(n-1, p) =>(n-1)! * p • So: fact2_aux(n, p) => fact2_aux(n-1, n*p) => (n-1)! * (n*p) = (n*(n-1)!)*p = n!*p Lecture 8: Recursion
Example: fact2 (cont) • Proving termination by induction: • Base case: fact2_aux(0, p) => return p • Inductive case: terminates if operator * terminates Lecture 8: Recursion
7 head null 10 15 5 head -1 null Proving Properties of Programs that use Lists • List concatenation: A^B Lecture 8: Recursion
Append • Input: Two lists x and y • Output: x ^ y List append (List x, List y) { if (x == null) return y; else return new List(x.value(), append(x.next(), y)); } Lecture 8: Recursion
Append (cont) • Want to prove: • Terminates • Returns x^y • Cost is O(n) Lecture 8: Recursion
Termination • Base case: x.length()=0 • append(x,y) => append(null, y) => y • Inductive case: x.length()=k-1>0 • append(x’,y) terminates for x’ with x’.length() < k • append(x.next(),y) terminates • constructor terminates Lecture 8: Recursion
Correctness of append • Base case: x.length()=0 • append(x,y) => append(null, y) = empty list ^ y = x ^ y • Inductive case: x.length()=k-1>0 • assumption: append(x.next(), y) => (list referred to by x.next) ^ y • new List(x.value(), append(x.next(), y)) => x ^ y Lecture 8: Recursion
Cost of append • Let Cost[n,m] be the number of operations for append(x,y) with x.length()=n and y.length() = m • Cost[0,m] = A (constant) • For n>0, Cost[n,m] = Cost[n-1,m] + B • Thus, Cost[n,m] = A + nB = O(n) Lecture 8: Recursion
7 head null 10 15 7 15 10 head null Reversing Lists • List reversal: Rev[x] Lecture 8: Recursion
Reversing Lists: first implementation List reverse1 (List l) { if (l == null) return null; else { return append(reverse(l.next()), new List(l.value())); } } Lecture 8: Recursion
Cost of reverse1 • Cost[n] is cost of reversing list with length n • Cost[0] = A (constant) • Cost[n] = B + Cost[n-1] + (n-1)C • Solving the recurrence gives Cost[n] = O(n^2) Lecture 8: Recursion
Tail recursive reverse • We can easily get an O(n) implementation using tail recursion: List rev2_aux(List x, List y) { if (x==null) return y; else return rev2_aux(x.next(), new List(x.value(), y)) } List reverse2(x) { return rev2_aux(x, null); } Lecture 8: Recursion
Cost of rev2_aux • Let Cost[n,m] be the number of operations for rev2_aux(x,y) with x.length()=n and y.length() = m • Cost[0,m] = A (constant) • n>0, Cost[n,m] = Cost[n-1,m+1] + B • Thus, Cost[n,m] = A + nB = O(n) Lecture 8: Recursion
Fibonacci Numbers • Want fib(0)=1, fib(1)=1, fib(n) = fib(n-1) + fib(n-2) if n>1 • Simple recursive implementation: // Precondition: n>=0 int fib(int n) { if (n < 2) return 1; else return fib(n-1)+fib(n-2); } Lecture 8: Recursion
Fibonacci Numbers • Cost is the same as the Fibonacci numbers themselves! • fib(n) rises exponentially with n: • fib(n) > (1.618)^n / 2 Lecture 8: Recursion
Imperative version int i=1; int a=1, b=1; while (i<n) { int c = a+b; // fib(i+1) a = b; b = c; i++; } Lecture 8: Recursion
Recursive Version • Define an auxiliary function fib_aux • Use two accumulator variables, one set to fib(i-1), the other to fib(i) Lecture 8: Recursion
Recursive Version int fib_aux (int n, int i, int x, int y) { if (i==n) return y; else return fib_aux(n, i+1, y, x+y); } int fib(int n) { if (n==0) return 1; else return fib_aux(n, 1, 1, 1); } Lecture 8: Recursion
Backtracking Search General pattern: • Test if current position satisfies goal • If not, mark current position as visited and make a recursive call to search procedure on neighboring points • Exhaustive search, terminates as soon as goal is found Lecture 8: Recursion
Backtracking search: simple example The “bear game”: • Start with initial number of bears • Need to reach goal number within a certain number of steps • Possible moves: • Add incr number of bears • Divide current number in half Lecture 8: Recursion
Backtracking search: simple example Public static boolean bears (int initial, int goal, int incr, int n) { if (initial == goal) return true; else if (n==0) return false; else if (bears(initial+incr, goal, incr, n-1)) return true; else if (initial % 2 == 0) return bears(initial/2, goal, incr, n-1); else return false; } Lecture 8: Recursion
Backtracking search: simple example • Why does this program terminate? • What if we remove the restriction on the number of steps? Lecture 8: Recursion
Benefits of Recursion • Recursion can often be implemented efficiently • Requires less work (programming and computation) • Tail recursive versions require less stack space • The code is typically much cleaner than imperative versions Lecture 8: Recursion