380 likes | 485 Views
Data Structures and Algorithms for Information Processing. 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.
E N D
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