Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Recursion • • • • • • Recursive Thinking Recursive Programming Recursion versus Iteration Direct versus Indirect Recursion More on Project 2 Reading L&C 10.1 – 10.4 1 Recursive Thinking • Many common problems can be stated in terms of a “base case” and an “inferred sequence of steps” to develop all examples of the problem statement from the base case • Let’s look at one possible definition of a comma separated values (.csv) list: – A list can contain one item (the base case) – A list can contain one item, a comma, and a list (the inferred sequence of steps) 2 Recursive Thinking • The above definition of a list is recursive because the second portion of the definition depends on there already being a definition for a list • The second portion sounds like a circular definition that is not useful, but it is useful as long as there is a defined base case • The base case gives us a mechanism for ending the circular action of the second portion of the definition 3 Recursive Thinking • Using the recursive definition of a list: A list is a: number A list is a: number comma list • Leads us to conclude 24, 88, 40, 37 is a list number comma list 24 , 88, 40, 37 number comma list 88 , 40, 37 number comma list 40 , 37 number 37 4 Recursive Thinking • Note that we keep applying the recursive second portion of the definition until we reach a situation that meets the first portion of the definition (the base case) • Then we apply the base case definition • What would have happened if we did not have a base case defined? 5 Infinite Recursion • If there is no base case, use of a recursive definition becomes infinitely long and any program based on that recursive definition will never terminate and produce a result • This is similar to having an inappropriate or no condition statement to end a “for”, “while”, or “do … while” loop 6 Recursion in Math • One of the most obvious math definitions that can be stated in a recursive manner is the definition of integer factorial • The factorial of a positive integer N (N!) is defined as the product of all integers from 1 to the integer N (inclusive) • That definition can be restated recursively 1! = 1 N! = N * (N – 1)! (the base case) (the recursion) 7 Recursion in Math • Using that recursive definition to get 5! 5! = 5 * (5-1)! 5! = 5 * 4 * (4-1)! 5! = 5 * 4 * 3 5! = 5 * 4 * 3 5! = 5 * 4 * 3 5! = 5 * 4 * 3 5! = 120 * (3-1)! * 2 * (2-1)! * 2 * 1! (the base case) * 2 * 1 8 Recursive Programming • Recursive programming is an alternative way to program loops without using “for”, “while”, or “do … while” statements • A Java method can call itself • A method that calls itself must choose to continue using either the recursive definition or the base case definition • The sequence of recursive calls must make progress toward meeting the definition of the base case 9 Recursion versus Iteration • We can calculate 5! using a loop int fiveFactorial = 1; for (int i = 1; i <= 5; i++) fiveFactorial *= i; • Or we can calculate 5! using recursion int fiveFactorial = factorial(5); . . . private int factorial(n) { return n == 1? 1 : n * factorial(n – 1); } 10 Recursion versus Iteration main return 120 factorial return 24 factorial(5) factorial return 6 factorial(4) factorial return 2 factorial(3) factorial return 1 factorial(2) factorial factorial(1) 11 Recursion versus Iteration • Note that in the “for” loop calculation, there is only one variable containing the factorial value in the process of being calculated • In the recursive calculation, a new variable n is created on the system stack each time the method factorial calls itself • As factorial calls itself proceeding toward the base case, it pushes the current value of n-1 • As factorial returns after the base case, the system pops the now irrelevant value of n-1 12 Recursion versus Iteration • Note that in the “for” loop calculation, there is only one addition (i++) and a comparison (i<=5) needed to complete each loop • In the recursive calculation, there is a comparison (n==1) and a subtraction (n 1), but there is also a method call/return needed to complete each loop • Typically, a recursive solution uses both more memory and more processing time 13 than an iterative solution Calling main( ) Recursively • Any Java method can call itself • Even main() can call itself as long as there is a base case to end the recursion • You are restricted to using a String [] as the parameter list for main() • The JVM requires the main method of a class to have that specific parameter list 14 Calling main( ) Recursively public class RecursiveMain { public static void main(String[] args) { if (args.length > 1) { String [] newargs = new String[args.length - 1]; for (int i = 0; i < newargs.length; i++) newargs[i] = args[i + 1]; main(newargs); // main calls itself with a new args array } System.out.println(args[0]); return; } } java RecursiveMain computer science is fun fun is science computer 15 Recursion (Continued) • Tail Recursion versus Iterative Looping • Using Recursion – Printing numbers in any base – Computing Greatest Common Denominator – Towers of Hanoi • Analyzing Recursive Algorithms • Misusing Recursion – Computing Fibonacci numbers • The Four Fundamental Rules of Recursion • Reading L&C 10.1 – 10.4 16 Tail Recursion • If the last action performed by a recursive method is a recursive call, the method is said to have tail recursion • It is easy to transform a method with tail recursion to an iterative method (a loop) • Some compilers actually detect a method that has tail recursion and generate code for an iteration to improve the performance 17 Tail Recursion • Here is a method with tail recursion: public void countdown(int integer) { if (integer >= 1) { System.out.println(integer); countdown(integer – 1); } } • Here is the equivalent iterative method: public void countdown(int integer) { while(integer >=1) { System.out.println(integer); integer--; } } 18 Tail Recursion • As you can see, conversion from tail recursion to a loop is straightforward • Change the if statement that detects the base case to a while statement with the same condition • Change recursive call with a modified parameter value to a statement that just modifies the parameter value • Leave the rest of the body that same 19 Printing an Integer in any Base • Hard to produce digits in left to right order – Must generate the rightmost digit first – Must print the leftmost digit first • Basis for recursion Last digit = n % base Rest of digits = n / base (base case) (recursion) 20 Printing an Integer in any Base • Table of digits for bases up to 16 private final String DIGIT_TABLE = "0123456789abcdef"; • Recursive method private void printInt(int n, int base) { if (n >= base) printInt( n/base, base ); System.out.print( DIGIT_TABLE.charAt(n % base)); } 21 Computing GCD of A and B • Basis for recursion GCD (a, 0) = a GCD (a, b) = GCD (b, a mod b) (base case) (recursion) • Recursive method private static int gcd(int a, int b) { if (b == 0) return a; else return gcd(b, a % b); } 22 Towers of Hanoi • The Towers of Hanoi puzzle was invented in the 1880’s by a French mathematician, Edouard Lucas • There are three upright pegs and a set of disks with holes to fit over the pegs • Each disk has a different diameter and a disk can only be put on top of a larger disk • Must move a pile of N disks from a starting tower to an ending tower one at a time 23 Towers of Hanoi • While solving the puzzle the rules are: – We can only move one disk at a time – We cannot place a larger disk on top of a smaller disk – All disks must be on some peg except for the one in transit • See example for three disks 24 Towers of Hanoi Original Configuration After First Move After Fourth Move After Fifth Move After Second Move After Sixth Move After Third Move After Last Move 25 Towers of Hanoi • The recursive solution is based on: Move one disk from start to end (base case) Move a tower of N-1 disks out of the way (recursion) private void moveTower (int numDisks, int start, int end, int temp) { if (numDisks == 1) moveOneDisk (start, end); else { moveTower(numDisks-1, start, temp, end); moveOneDisk(start, end); moveTower(numDisks-1, temp, end, start); } } 26 Misusing Recursion • Some algorithms are stated in a recursive manner, but they are not good candidates for implementation as a recursive program • Calculation of the sequence of Fibonacci numbers Fn (which have many interesting mathematical properties) can be stated as: F0 = 0 F1 = 1 Fn = F(n-1) + F (n-2) (one base case) (another base case) (the recursion) 27 Misusing Recursion • We can program this calculation as follows: public int { if (n <= return else return } fib(int n) 1) n; fib(n – 1) + fib(n – 2); • Why is this not a good idea? 28 Misusing Recursion • If we trace the execution of this recursive solution, we find that we are repeating the calculation for many instances of the series F5 F4 F3 F2 F1 F3 F2 F1 F1 F2 F0 F1 F1 F0 F0 29 Misusing Recursion • Note that in calculating F5, our code is calculating F3 twice, F2 three times, F1 five times, and F0 three times • These duplicate calculations get worse as the number N increases • The order of the increase of time required with the value of N is exponential • For N = 40, the total number of recursive calls is more than 300,000,000 30 Misusing Recursion • This loop solution (for N >= 2) is O(n) : public int fibonacci(int n) { int fNminus2 = 0, fNminus1 = 1; int fN = 0; for (int i = 2; i <= n; i++) { fN = fNminus1 + fNminus2; fNminus2 = fNminus1; fNminus1 = fN; } return fN; } 31 Four Fundamental Rules of Recursion • Base Case: Always have at least one case that can be solved without recursion • Make Progress: Any recursive call must make progress toward a base case • You gotta believe: Always assume that the recursive call works • Compound Interest: Never duplicate work by solving the same instance of a problem in separate recursive calls Ref: Mark Allen Weiss, Data Structures & Problem Solving using Java, Chapter 7 32 Introduction to Project 4 • Evaluate LISP Expressions (Prefix Notation) • Name LISP comes from “LISt Processor” • Name is sometimes ridiculed as: “Lots of Irritating and Silly Parentheses” • In LISP, all loops are done using recursion! • Infix Notation Prefix Notation 2+2 2 * ( 3 + 4) (1+2)*(3-4) (+2 2 ) (* 2(+3 4)) ( * ( + 1 2 ) (- 3 4 ) ) Introduction to Project 4 • The Evaluator class for project 4 needs a recursive evaluate() method • When the evaluate method calls itself: – the current context of all local variable is left on the system stack – a new context for all local variables is created on the top of the system stack • The return from evaluate() dissolves the current context and pops the previous level context off the system stack Calling Sequence / System Stack ( main return 4.0 + 1 3 ) evaluate(null) evaluate(new q) evaluate evaluate evaluate(q) return 4.0 return 4.0 evaluate return 4.0 System Stack [null] [‘+’] [‘+’, 1] evaluate(q) [‘+’, 1, 3] [empty] [null] [null] evaluate return 4.0 [null] [null] evaluate(q) [empty] [empty] evaluate [empty] Return on ‘)’ (Base Case) Calling Sequence / System Stack ( * evaluate return 4.0 * ? ( + 1 3 ) Next Operand OR ‘(‘ or ‘)’ evaluate(q) evaluate(new q) evaluate evaluate(q) evaluate return ? evaluate evaluate(q) return 4.0 return 4.0 evaluate return 4.0 [‘*’] [empty] [‘*’] [empty] [‘*’] [empty] [‘*’] [empty] [‘*’, 4.0] [empty] [‘*’, 4.0] evaluate evaluate(q) return 4.0 System Stack [empty] evaluate(q) [‘+’] [‘+’, 1] [‘+’, 1, 3] [empty] [empty] [empty] [‘*’, 4.0, ?] evaluate [empty] Return on ‘)’ (Base Case)