1 Recursion Recursive Thinking Recursive Programming Recursion versus Iteration Direct versus Indirect Recursion More on Project 2 Reading L&C 10.1 – 10.4
2 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)
3 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
4 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
5 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?
6 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
7 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(the base case) N! = N * (N – 1)!(the recursion)
8 Recursion in Math Using that recursive definition to get 5! 5! = 5 * (5-1)! 5! = 5 * 4 * (4-1)! 5! = 5 * 4 * 3 * (3-1)! 5! = 5 * 4 * 3 * 2 * (2-1)! 5! = 5 * 4 * 3 * 2 * 1! (the base case) 5! = 5 * 4 * 3 * 2 * 1 5! = 120
9 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
10 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); }
11 Recursion versus Iteration factorial(5) main factorial factorial(4) factorial(3) factorial(2) factorial(1) return 1 return 2 return 6 return 24 return 120
12 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
13 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 than an iterative solution
14 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
15 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
16 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
17 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
18 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--; }
19 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
20 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 (base case) Rest of digits = n / base(recursion)
21 Printing an Integer in any Base Table of digits for bases up to 16 private final String DIGIT_TABLE = " abcdef"; Recursive method private void printInt(int n, int base) { if (n >= base) printInt( n/base, base ); System.out.print( DIGIT_TABLE.charAt(n % base)); }
22 Computing GCD of A and B Basis for recursion GCD (a, 0) = a (base case) GCD (a, b) = GCD (b, a mod b) (recursion) Recursive method private static int gcd(int a, int b) { if (b == 0) return a; else return gcd(b, a % b); }
23 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
24 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
25 Towers of Hanoi Original Configuration After First Move After Second Move After Third Move After Fourth Move After Fifth Move After Sixth Move After Last Move
26 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); }
27 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 F n (which have many interesting mathematical properties) can be stated as: F 0 = 0(one base case) F 1 = 1(another base case) F n = F (n-1) + F (n-2) (the recursion)
28 Misusing Recursion We can program this calculation as follows: public int fib(int n) { if (n <= 1) return n; else return fib(n – 1) + fib(n – 2); } Why is this not a good idea?
29 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 F4F3 F2 F1 F0 F2 F1F0 F1
30 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
31 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; }
32 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
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)( * 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 evaluate(null) main evaluate evaluate(new q) return 4.0 ( ) System Stack evaluate return 4.0 evaluate(q) [null] [‘+’][‘+’, 1] Return on ‘)’ (Base Case) evaluate return 4.0 evaluate return 4.0 evaluate(q) [‘+’, 1, 3][empty] [null] [empty]
Calling Sequence / System Stack evaluate(q) evaluate evaluate(new q) return 4.0 return 4.0 * ? ( * ( ) System Stack evaluate return 4.0 evaluate(q) [‘*’] Return on ‘)’ (Base Case) evaluate return 4.0 evaluate return 4.0 evaluate(q) [‘*’, 4.0] evaluate(q) Next Operand OR ‘(‘ or ‘)’ evaluate return ? evaluate [‘*’, 4.0][‘*’, 4.0, ?] [empty] [‘+’] [empty] [‘+’, 1][‘+’, 1, 3] [‘*’] [empty]