290 likes | 422 Vues
This document outlines essential concepts of recursion in programming, including its applications, properties verification, and different approaches such as imperative and applicative programming styles. It critically analyzes recursive functions with examples like factorial and Fibonacci, discussing the advantages of tail recursion for optimization. Furthermore, it addresses the importance of debugging, testing, and program correctness assurances, while providing insights into using recursion to solve complex problems through a structured approach.
E N D
Data Structures and Algorithms for Information Processing Some Notes on Recursion Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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); } Some Notes on Recursion
Tail Recursion • Tail recursive factorial static int fact2(int n) { // Precondition: 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); } Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on 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 Some Notes on Recursion
Example: fact2 (cont) • Proving termination by induction: • Base case: fact2_aux(0, p) => return p • Inductive case: terminates if operator * terminates Some Notes on 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); } Some Notes on 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) Some Notes on 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); } Some Notes on Recursion
Fibonacci Numbers • Cost is the same as the Fibonacci numbers themselves! • fib(n) rises exponentially with n: • fib(n) > (1.618)^n / 2 Some Notes on 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++; } Some Notes on Recursion
Recursive Version • Define an auxiliary function fib_aux • Use two accumulator variables, one set to fib(i-1), the other to fib(i) Some Notes on 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); } Some Notes on 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 Some Notes on 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 • If even divide current number in half • Suppose initial=10, goal=100, incr=50,n=5 Some Notes on 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; } Some Notes on Recursion
Backtracking search: simple example • Why does this program terminate? • What if we remove the restriction on the number of steps? Some Notes on 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 Some Notes on Recursion