1 / 35

Recursion

Learn about the general form of recursive methods, explore simple recursion examples, simulate recursive methods by hand, prove their correctness, and model recursion through onions. Discover the benefits and challenges of recursion and compare it to iterative methods. Gain insights into information hiding and stepping through called functions. Master the four rules of recursion and apply them to solve problems like factorial and power calculations.

mwinkler
Download Presentation

Recursion

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Recursion nGeneral Form of Recursive methods nExamples of Simple Recursion nHand Simulating Recursive methods nProving Recursive methods Correct nModeling Recursion via Onions nRecursive methods on Naturals/Integers

  2. Recursion • Recursion: a call to a method appears in that method’s body (this is called direct recursion) • Once you understand recursive methods, they are often simpler to write than their iterative equivalents • A bit slower (maybe 5%) than equivalent iterative methods; • Typical application, 5% is insignificant (most of the time will be taken up elsewhere anyway)

  3. Fund Raising: Iteration vs. Recursion Problem: Collect $1,000.00 for charity Assumption: Everyone is willing to donate a penny • Iterative Solution • Visit 100,000 people, asking each for a penny • Recursive Solution • If you are asked to collect a penny, give a penny to the person who asked you for it. • Otherwise • Visit 10 people and ask them to each raise 1/10th of the amount of money that you have been asked to raise • Collect the money that they give you and combine it into one bag • Give it to the person who asked you to collect the money

  4. General Form of Recursive methods Solve(Problem) { if (Problem is minimal/not decomposable: a base case) solve Problem directly; i.e., without recursion else { (1) Decompose Problem into one or more similar, strictly smaller subproblems: SP1, SP2, ... , SPN (2) Recursively call Solve (this method) on each subproblem: Solve(SP1), Solve(SP2),..., Solve(SPN) (3) Combine the solutions to these subproblems into a solution that solves the original Problem} }

  5. Understanding recursion • The usual way to teach recursion is to “trace through” a recursion, seeing what it does at each level • This may be a good way to understand how recursion works... • ...but it's a terrible way to try to use recursion • There is a better way

  6. Base cases and recursive cases • Every valid recursive definition consists of two parts: • One or more base cases, where you compute the answer directly, without recursion • One or more recursive cases, where you do part of the work, and recur with a simpler problem

  7. Information hiding • Can you understand this function without looking at sort? • int spread (int A[], int size) { int max, min; sort(A, size); min = A[0]; max = A[size - 1]; return max - min;}

  8. Stepping through called functions • Functions should do something simple and understandable • When you try to understand a function, you should not have to step through the code of the functions that it calls • When you try to understand a recursive function, you should not have to step through the code of the functions it calls

  9. We have small heads • It's hard enough to understand one level of one function at a time • It's almost impossible to keep track of many levels of the same function all at once • But you can understand one level of one function at a time... • ...and that's all you need to understand in order to use recursion well

  10. The four rules • Do the base cases first • Recur only with a simpler case • Don't use global or reference variables • Don't look down

  11. Do the base cases first • Every recursive function must have some things it can do without recursion • These are the simple, or base, cases • Test for these cases, and do them first • This is just writing ordinary, nonrecursive code

  12. Recur only with a simpler case • If the problem isn't simple enough to be a base case, break it into two parts: • A simpler problem of the same kind (for example, a smaller number, or a shorter list) • Extra work not solved by the simpler problem • Combine the results of the recursion and the extra work into a complete solution • “Simpler” means “more like a base case”

  13. factorial: In Mathematics and Java Example using Definition 4! = 4 * 3! = 4 * 3 * 2! = 4 * 3 * 2 * 1! = 4 * 3 * 2 * 1 * 0! = 4 * 3 * 2 * 1 * 1 1 if N = 0 N! = N*(N-1)! if N > 0 int factorial (int n) { if (n == 0) //Non-decomposable return 1; else { int subN = n-1; //Decompose int subFact = factorial(subN); //Solve Subproblem return n * subFact; //Combine } }

  14. Simplified and Iterative factorial int factorial (int n) { if (n == 0) return 1; else return n * factorial(n-1); } int factorial (int n) { int answer = 1; for (int i=1; i<=n; i++) answer *= i; return answer; } Here we combine the three steps in the previous else block into a single return statement (one expression including the decompose, solve, and combine steps). Compare this method to the one below it, which operates by iteration. The iterative version requires the declaration of two variables and the use of state change operators (which are always difficult to reason about)

  15. pow: Raising a Number to a Power 1 if N = 0 AN = A*AN-1 if N > 0 double pow(double a, int n) { if (n == 0) return 1.; else return a*pow(a,n-1) } Example using Definition A4 = A * A3 = A * A* A2 = A * A* A* A1 = A * A* A* A* A0 = A * A* A* A* 1 Calling pow(a,n)requires exactly n multiplications The pow in the Java Math library actually computes its answer using logarithms and exponentials

  16. Recursion: Apartment complex analogy • Person in an apartment: Unit of method invocation • That person can be called to compute an answer; once he/she computes the answer (possibly helped by calling another person in the apartment complex), he/she places a return call, and reports back the answer to the person who called him/her • Each person knows the phone number of the person living in the apartment underneath them • who also has the same set of instructions: so any person can call the one underneath to solve a simpler problem • The first call initiates a sequence of calls to people living in lower level apartments (each person solving a simpler problem), whose answers percolate back to the top, finally solving the original problem

  17. Hand Simulation of Factorial n = return = n = return = n = return = n = return = ... n = return =

  18. Proof Rules for Recursive Methods To prove that a recursive method, Solve, is correct: • Prove that Solve computes (without recursion) the correct answer to any minimal (base case) Problem • Base cases are simple, so this should be easy • Prove that the argument to any recursive call of Solve, e.g. SPI, is strictly smaller (closer to the minimal case) than Problem • The notion of strictly smaller should be easy to understand for the recursive argument, so this should be easy • Prove that these answers are combined to compute the correct answer for Problem • In this proof, you may assume that each recursive call, Solve(SPI), correctly computes its answer • The assumption makes this proof easy Recursion is computable induction (a math concept)

  19. Applying the Proof Rules factorial (recurring on the parameter n) • factorial correctly returns 1 for the minimal argument 0. • In factorial(n-1), n-1 is always closer to 0 than n is. • Assume factorial(n-1) computes the correct answer. • n times this value is, by definition, the correct value of factorial(n) pow (recurring on the parameter n) • pow correctly returns 1 for the minimal argument 0. • In pow(a,n-1), n-1 is always closer to 0 than n is. • Assume pow(a,n-1) computes the correct answer, an-1. • a*pow(a,n-1) is a*an-1 is an, the correct value of pow(a,n)

  20. Proof Rules and Bad Factorials int factorial (int n) { if (n == 0) return 0; //0! is not 0; else return n*factorial(n-1); } int factorial (int n) { if (n == 0) return 1; else return factorial(n+1)/(n+1); //n+1 not closer to 0 } int factorial (int n) { if (n == 0) return 1; else return n + factorial(n-1); //n+(n-1)! is not n! }

  21. Proof Rules and Bad Factorials (cont) • In the first method, the wrong answer (0) is returned for the base case; since everything depends on the base case, ultimately this method always returns 0 • In the second method, n+1 is farther away from the base case: this method will continue calling factorial with ever larger arguments, until the maximum int value is exceeded: a runaway (or “infinite”) recursion (actually, each recursive call can take up some space, so eventually memory is exhausted). • In the third method, the wrong answer is returned by incorrectly combining n and the solved subproblem; this method returns one more than the sum of all the integers from 1 to n (an interesting method in its own right) not the product of these values • In the first method, it always returns the wrong answer; the second method never returns an answer, the third method returns the correct answer only in the base case

  22. fpow: a faster version of pow Note: if N is odd N/2 truncates: so 7/2 = 3 1 if N = 0 AN = B2 where B = AN/2 if N > 0 and N is Even AB2 where B = AN/2 if N > 0 and N is Odd int fpow(double a, int n) { if (n == 0) return 1.; else { double b = fpow(a,n/2); if (n%2 == 0) return b*b; else return a*b*b; } } Calling fpow(a,n)requires between at least log2n and at most 2log2n multiplications Compare this complexity to calling pow(a,n)requiring n multiplications Contemporary cryptography raises large numbers to huge (hundred digit) powers; it needs a fast method

  23. Recursive Definition of Naturals • 0 is the smallest Natural • It is minimal, and cannot be decomposed • z(x) is true if x is zero and false if x is non-zero • The successor of a Natural is a Natural • If x is a Natural, s(x) is a Natural • The successor of x is x+1 • If x is a non-0 Natural, p(x) is a Natural • The predecessor of x is x-1 • p(0) throws an exception Note: z(s(x)) is always false p(s(x)) = x s(p(x)) = x only if X  0

  24. s(s(s(0))) = 3 s(s(0)) = 2 s(0) = 1 0 Onion Model of Recursive Naturals Every Onion is either Base Case : The core Recursive Case: A layer of skin around a smaller onion (which may be the core, or some deeper layer) The value of an onion is the number of layers of skin beyond the core

  25. Computation with Naturals Let’s Define 3 methods that operate on Naturals in Java int s(int x) {return x+1;} int p(int x) { if (x==0) throw new IllegalArgumentException("p with x=0"); return x-1; } bool z(int x) {return x == 0;} We can define all common operators (both arithmetic and relational) on Naturals by writing simple recursive methods that use these three methods

  26. Recursive methods on Naturals int sum(int a, int b) { if (z(a)) return b; else return s(sum(p(a),b)); //1 + (a-1 + b) } int sum(int a, int b) { if (z(a)) return b; else return sum(p(a), s(b)); //(a-1) + (b+1) } In both sum methods, a gets closer to 0 in recursive calls

  27. Recursive methods on Naturals II int product(int a, int b) { if (z(a)) return 0; else return sum(b,product(p(a),b)) //b + (a-1)*b } int power(int a, int b) { if (z(b)) return 1; else return product(a, power(a,p(b)));//a * ab-1 }

  28. Recursive methods on Naturals III bool equal(int a, int b) //Is a == b { if (z(a) || z(b)) //If either is 0... return z(a) && z(b); //return whether both are 0 else return equal(p(a), p(b)); //Is (a-1) == (b-1) } bool less(int a, int b) //Is a < b { if (z(b)) //Nothing is < 0 return false; else if (z(a)) //If b!=0, a==0, then a<b return true; else return less(p(s),p(b)); //Is (a-1) < (b-1) }

  29. Recursive methods on Naturals IV bool even(int a) { if (z(a)) //True if 0 return true; else //Opposite of even(a-1) return !even(p(a)) } bool odd(int a) { if (z(a)) //False if 0 return false; else //Opposite of odd(a-1) return !odd(p(a)); }

  30. Printing an Integer’s Digits void print (int i) { if (i < 10) //if (i >= 10) System.out.print(i); // print(i/10); else { //System.out.print(i%10); print(i/10); System.out.println(i%10); } } • print correctly prints all 1-digit values: 0 - 9. • In print(i/10), i/10 is one digit closer to 0 - 9 than i is. • Assume print (i/10) correctly prints all the digits in i/10 in the correct order • Printing i/10 (all digits but the last) and then printing i%10 (the last digit), prints all the digits in i in order The code above is simplified by bottom factoring and test reversal and noting for i<10, i is i%10

  31. Printing an Integer’s Digits in Reverse void printReversed (int i) { if (i < 10) //System.out.print(i%10); System.out.print(i); //if (i >= 10) else { // System.out.print(i/10); System.out.print(i%10); printReversed(i/10); } } • printReversed correctly prints all 1-digit values: 0 - 9. • In printReversed(i/10), i/10 is closer to 0-9 than i is. • Assume printReversed(i/10) correctly prints all the digits in i/10 reversed. • Printing the last digit, i%10, and then printing the i/10 reversed prints all the digits in i reversed.

  32. Converting an int to a string String toString (int i) { if (i < 10) return ""+(char)(i+’0’); else return toString(i/10) + (char)(i%10+’0’); } • toString correctly returns all 1-digit values: 0 - 9. • In toString(i/10), i/10 is one digit closer to 0-9 than i is. • Assume toString(i/10) correctly returns a String of all the digits in I/10. • Then, after appending the char equivalent of i%10 at the end of this String correctly stores the String equivalent of i.

  33. Synthesizing Recursive Methods • Find the base (non-decomposable) case(s) • Write the code that detects the base case(s) and returns the correct answer for them without using recursion • Assume that you can decompose all non base-case(s) and then solve these smaller subproblems via recursion • Choose the decomposition • There should be some natural decomposition • Write code that combines these solved subproblems (often there is just one) to solve the original problem • Typical decompositions • Integers : i - something or i/something (digit at a time) • Linked Lists: l.next

  34. Problems • Write the following methodpublic static String reverse (String s) recursively. Choose an appropriate base case, recursive call (hint: substring method using one parameter), and a way to combine (hint: +) solved smaller problems to solve the original problem.reverse("abcd") should return "dcba" • Write the following methodpublic static int toInt (String s) recursively. It is like parseInt, but only for non-negative valuestoInt("138") should return 138

  35. Problems (continued) • Write the following methodpublic static boolean equals (String s1, String s2) recursively. Its recursive structure is like that of the equals methods for ints. • Write the following methodpublic static int compare (String s1, String s2) recursively. Its recursive structure is similar to equals, above.

More Related