Download presentation
Presentation is loading. Please wait.
Published byAnthony Stanley Modified over 9 years ago
1
Chapter 11 – Recursion Recursive Processes Writing a Recursive Method
A Recursive Factorial Method Comparison of Recursive and Iterative Solutions Recursive Method Evaluation Practice Binary Search Merge Sort Towers of Hanoi Drawing Trees with a Fractal Algorithm Performance Analysis
2
Recursive Processes 2 A recursive process calls itself. It’s a picture in a picture.
3
Relayed Flag Signals Imagine a line of old-time warships.
3 Imagine a line of old-time warships. The admiral at one end wants confidential casualty information. A flag signal can be read only by an adjacent ship. Transmitted signal: “Report your casualties plus casualties in all further-away ships.” Returned signal: “My casualties plus casualties in all further-away ships are ...”
4
Writing a Recursive Method
4 Determine how to split the problem into successively smaller sub-problems. Identify the stopping condition or base case. The calling process must not skip over the stopping condition, and it is not good enough to just approach it asymptotically. It must actually reach it. The recursive method body needs an if statement whose condition is the stopping condition: if (<stopping-condition>) Solve local problem and return. else Make next recursive call(s). Process information returned by recursive calls and return.
5
A Recursive Factorial Method
5 Formula for the factorial of a number n: n! = n * (n-1) * (n-2) * ... * 2 * 1 Example: 5! = 5 * 4 * 3 * 2 * 1 = 120 Likewise: 4! = 4 * 3 * 2 * 1 = 24 Therefore: 5! = 5 * 4! And by induction: n! = n * (n-1)! This formula is the recurrence relation for a factorial. The stopping condition is when n equal 1.
6
A Recursive Factorial Method
6 10 public static void main(String[] args) 11 { 12 System.out.println(factorial(5)); 13 } // end main public static int factorial(int n) 16 { 17 int nF; // n factorial 18 if (n == 0 || n == 1) 19 { 20 nF = 1; 21 } 22 else 23 { 24 nF = n * factorial(n-1); 25 } 26 return nF; 27 } // end factorial
7
Recursive Factorial Method Trace
7 Factorial line# factorial n nF output 12 5 ? 24 4 3 2 1 20 6 120
8
Cleaner Recursive Factorial Method
8 We used the local variable, nF, just to give substance to the trace. In practice, that local variable is not necessary, and we would simplify the method to this: public static int factorial(int n) { if (n == 0 || n == 1) return 1; } else return n * factorial(n-1); } // end factorial
9
An Iterative Factorial Method
9 Alternately, we can reverse the order of multiplication and write the formula for a factorial like this: n! = 1 * 2 * ... * (n-2) * (n-1) * n This suggests an iterative solution like this: public static int factorial(int n) { int factorial = 1; // the factorial value so far for (int i=2; i<= n; i++) factorial *= i; } return factorial; } // end factorial
10
Characteristics of Recursive Solutions
10 All recursive programs can be converted to iterative programs that use loops instead of recursive method calls. With some problems, a recursive solution is more straightforward than an iterative solution. But recursive calls take a lot of overhead. The computer must: Save the calling module’s local variables. Find the method. Make copies of call-by-value arguments. Pass the arguments. Execute the method. Find the calling module. Restore the calling module’s local variables. Recursion uses a built-in stack, and if there are too many recursive calls, this stack can overflow.
11
Different Kinds of Recursions
11 Mutual recursion is when two or more methods call each other in an alternating cycle of method calls. Binary recursion is when a method’s body includes two (or more) recursive calls to itself and the method executes both calls. Linear recursion is when a method executes just one recursive call to itself, as in a factorial recursion. Tail recursion is a special case of linear recursion. It’s when a recursive method executes its recursive call as its very last operation. The relayed flag signals and the recursive factorial calculation are not tail recursions, because other operations occur after the recursive calls. For example, in return n * factorial(n-1); a multiplication by n occurs after the recursive call. Tail recursions are the easiest to convert to iterations.
12
Converting from Iteration to Recursion
12 Suppose you have a method that uses iteration to print a string in reverse order, like this: private static void printReverseMessage(String msg) { int index; // position of character to be printed index = msg.length() - 1; while (index >= 0) System.out.print(msg.charAt(index)); index--; } } // end printReverseMessage
13
Converting from Iteration to Recursion
13 Since the iteration starts at the end and prints the current character before decrementing the character index, substitute a recursive method that prints the last character in a string before making a recursive call with the substring before that printed character: private static void printReverseMessage(String msg) { int index; // position of last character in msg if (!msg.isEmpty()) index = msg.length() - 1; System.out.print(msg.charAt(index)); printReverseMessage(msg.substring(0, index)); } } // end printReverseMessage
14
Recursive Evaluation Practice
14 Here’s a compact description of the factorial algorithm, using recurrence relations described in terms of mathematical functions: n * f(n-1) 1 for n > 1 for n <= 1 f(n) = To evaluate a recurrence relation by hand, use this procedure: Write the algorithm in function notation (as shown above). For the first line of your recursion trace, write the recurrence relation with the variables replaced by the initial numbers. Under that, write the recurrence relation for the first subordinate method call with the variables replaced by appropriately altered numbers on both left and right sides of the equations. Continue like this until you reach a stopping condition. On subsequent rows, re-write what you previously wrote on the rows above but in reverse order, replacing right-side unknowns with known values as you go.
15
Recursive Evaluation Practice − Factorial
15 f(5) = 5 * f(4) f(4) = 4 * f(3) f(3) = 3 * f(2) f(2) = 2 * f(1) f(1) = 1 f(2) = 2 * 1 ⇒ 2 f(3) = 3 * 2 ⇒ 6 f(4) = 4 * 6 ⇒ 24 f(5) = 5 * 24 ⇒ 120 call sequence return sequence stopping condition Factorial
16
Recursive Evaluation Practice − Bank Balance
16 Next, consider a function that returns the balance, b, in a bank account after n = 3 equal periodic deposits of amount D = 10, with interest rate R = 0.1 for the time period between deposits. Here’s the recurrence relation: B(n, D, R) = D + (1 + R) * b(n-1) for n >= 1 for n < 1 b(3, 10, 0.1) = * b(2, 10, 0.1) b(2, 10, 0.1) = * b(1, 10, 0.1) b(1, 10, 0.1) = * b(0, 10, 0.1) b(0, 10, 0.1) = 0 b(1, 10, 0.1) = * 0 ⇒ 10 b(2, 10, 0.1) = * 10 ⇒ 21 b(3, 10, 0.1) = * 21 ⇒ 33.1 call sequence return sequence stopping condition Bank Balance
17
Recursive Evaluation Practice with the Recurrence Relation Expressed as a Generic Math Function
17 Here’s a practice problem whose function has two parameters – x and y: f(x, y) = f(x-3, y-1) + 2 f(y, x) x > 0, x > y x > 0, x <= y x <= 0 f is a generic name for a function. To see how this works, evaluate f(5, 4): f(5, 4) = f(2, 3) + 2 f(2, 3) = f(3, 2) f(3, 2) = f(0, 1) + 2 f(0, 1) = 0 f(3, 2) = ⇒ 2 f(2, 3) ⇒ 2 f(5, 4) = ⇒ 4 Generic Example 1 call sequence return sequence stopping condition
18
Generic Recursive Evaluation Practice − Continued
18 Generic Recursive Evaluation Practice − Continued Sometimes stopping conditions are not adequate. For example, suppose you have this recurrence relation: f(x, A) = x > 1 x = 1 A * f(x-2, A) A Here’s how the evaluation of f(4, 3) would go: f(4, 3) = 3 * f(2, 3) f(2, 3) = 3 * f(0, 3) f(0, 3) = ? Generic Example 2 call sequence This skips the indicated stopping condition, and the recursion becomes unspecified.
19
Generic Recursive Evaluation Practice − Continued
19 Generic Recursive Evaluation Practice − Continued This example uses inequality, but it never reaches the stopping condition: f(x) = x > 0 x <= 0 f(x/2) Here’s an example where the stopping condition is a maximum. That’s OK, but there’s still a problem. Do you see the problem? f(x) = x < 3 x >= 3 f(x) + 1 4 The function gets larger, but the stopping condition does not look at the function. It just looks at x, and x never changes.
20
Starting from the Stopping Condition − the Fibonacci Sequence.
20 f(n) = f(n-1) + f(n-2) 1 n > 1 n = 1 n = 0 If all the useful work occurs in the return sequence, it’s a head recursion, and you can jump immediately to the stopping condition and evaluate from there. f(0) = 0 f(1) = 1 f(2) = f(1) + f(0) = ⇒ 1 f(3) = f(2) + f(1) = ⇒ 2 f(4) = f(3) + f(2) = ⇒ 3 f(5) = f(4) + f(3) = ⇒ 5 f(6) = f(5) + f(4) = ⇒ 8 ... straightforward evaluation Fibonacci Sequence
21
Binary Search 21 Suppose you want to find the location of a particular value in an array. With a sequential search, the number of steps equals the array length. If the array is already sorted, you can use a binary search, and then the number of steps is only log2(length). A binary search uses “divide-and-conquer”: Divide the array into two nearly equally sized sub-arrays, determine which sub-array contains the item if it is present, divide that sub-array into two equally sized sub-arrays, and so forth, until the sub-array has only one element. Binary-search recursive method: Parameters: the whole array, first and last indices of the sub-array, the target value. If first equals last, stop and see if that one element is the target value. Otherwise, recursively call the sub-array which might contain the target value.
22
Binary Search 22 public static int binarySearch( int[] arr, int first, int last, int target) { int mid, index; System.out.printf("first=%d, last=%d\n", first, last); if (first == last) // stopping condition if (arr[first] == target) return first; else return -1; } else // continue recursion mid = (last + first) / 2; if (target > arr[mid]) first = mid + 1; last = mid; return binarySearch(arr, first, last, target); } // end binarySearch
23
Binary Search 23 public static void main(String[] args) { int[] array = new int[] {-7, 3, 5, 8, 12, 16, 23, 33, 55}; System.out.println(BinarySearch.binarySearch( array, 0, (array.length - 1), 23)); array, 0, (array.length - 1), 4)); } // end main Sample session: first=0, last=8 first=5, last=8 first=5, last=6 first=6, last=6 6 first=0, last=4 first=0, last=2 first=2, last=2 -1
24
Merge Sort 24 For a binary search to work, the array must be already sorted. When an array is large, a merge sort is a relatively efficient sorting technique. A merge sort also used “divide-and-conquer”, but instead of making a recursive call for just one of each half, it makes recursive calls for both of them. Each recursive call divides the current part in half, until there is only one element, which represents the stopping condition for that recursive branch. Return sequences recombine elements by merging parts, two at a time, until everything is back together.
25
Merge Sort Method 25 public static int[] mergeSort(int[] array) { int half1 = array.length / 2; int half2 = array.length - half1; int[] sub1 = new int[half1]; int[] sub2 = new int[half2]; if (array.length <= 1) return array; } else System.arraycopy(array, 0, sub1, 0, half1); System.arraycopy(array, half1, sub2, 0, half2); sub1 = mergeSort(sub1); sub2 = mergeSort(sub2); array = merge(sub1, sub2); } // end mergeSort
26
Merge Method 26 private static int[] merge(int[] sub1, int[] sub2) { int[] array = new int[sub1.length + sub2.length]; int i1 = 0, i2 = 0; for (int i=0; i<array.length; i++) // both sub-groups have elements if (i1 < sub1.length && i2 < sub2.length) if (sub1[i1] <= sub2[i2]) array[i] = sub1[i1++]; else // sub2[i2] < sub1[i1] array[i] = sub2[i2++]; } else // only one sub-group has elements if (i1 < sub1.length) else // i2 < sub2.length } // end only one sub-group has elements } // end for all array elements return array; } // end merge
27
Merge Sort Driver and Output
27 public static void main(String[] args) { Random random = new Random(0); int length = 19; int[] array = new int[length]; for (int i=0; i<length; i++) array[i] = random.nextInt(90) + 10; printArray("initial array", array); printArray("final array", mergeSort(array)); } // end main private static void printArray(String msg, int[] array) System.out.println(msg); for (int i : array) System.out.printf("%3d", i); System.out.println(); } // end printArray Sample session: initial array final array
28
The Towers of Hanoi 28 According to legend, there is a temple in Hanoi which contains 64 golden disks, each with a different diameter, and each with a hole in its center. The disks are stacked on three towering posts. Initially, all the disks are stacked on post #1, with the largest-diameter disk on the bottom and progressively smaller-diameter disks placed on top of each other. The temple's monks are tasked with moving disks from post 1 to post 3, while obeying these rules: Only one disk can be moved at a time. No disk can be placed on top of a disk with a smaller diameter. Let's help the monks by writing a computer program that specifies the optimum transfer sequence.
29
The Towers of Hanoi 29 Recursive algorithm for moving n disks from the source tower to a destination tower: Move the top n - 1 disks to the intermediate tower. (This is a recursive step where n - 1 disks are moved.) Move the bottom disk to the destination tower. Move the intermediate-tower group to the destination tower. Write a recursive method named move that simulates the movement of n disks from one specified tower to another specified tower.
30
Drawing Trees with a Fractal Algorithm
31 Computer recursion mimics the recurrence of similar patterns in nature. To illustrate the analogy, we’ll now use recursion to draw a grove of trees, where each tree is a recursive picture. Our simulated trees will repeat a simple geometrical pattern − a straight section followed by a fork with two branches: The left branch goes 30 degrees to the left and has a length equal to 75% of the length of the straight section. The right branch goes 50 degrees to the right and has a length equal to 67% of the length of the straight section. An object created by repeating a pattern at different scales is called a fractal. A mathematical fractal displays self-similarity on all scales. In the natural world – and in computers – self-similarity exists only between upper and lower scale limits. In the case of a tree, the upper limit is the size of the trunk and the lower limit is the size of a twig. These upper and lower limits establish recursive starting and stopping conditions. In each of a sequence of steps, the program slightly modifies size attributes and repaints the scene. This makes the trees grow larger and fill out with more branches as time passes.
31
Drawing Trees with a Fractal Algorithm
32 This example also illustrates: GUI animation. The Model-View-Controller (MVC) design pattern. The program’s model is in the Tree class, which models the program’s key components. The program’s view is in the TreePanel class, which implements updates in the screen display. The program’s controller is in the TreeDriver class, which gathers user input and calls Tree and TreePanel methods to drive the model and manage alterations to the display.
32
The model – the Tree Class
33 import java.awt.Graphics; public class Tree { private final int START_X; // where the tree sprouts private final int START_TIME; // when the tree sprouts private final double MAX_TRUNK_LENGTH = 100; private double trunkLength; //********************************************************** public Tree(int location, int startTime, double trunkLength) this.START_X = location; this.START_TIME = startTime; this.trunkLength = trunkLength; } // end constructor // <get-methods-for: START_X, START_TIME, and trunkLength>
33
The Tree Class − continued
34 public void updateTrunkLength() { trunkLength = trunkLength * trunkLength * (1.0 - trunkLength / MAX_TRUNK_LENGTH); } // updateTrunkLength //***************************************************** public void drawBranches(Graphics g, int x0, int y0, double length, double angle) double radians = angle * Math.PI / 180; int x1 = x0 + (int) (length * Math.cos(radians)); int y1 = y0 - (int) (length * Math.sin(radians)); if (length > 2) g.drawLine(x0, y0, x1, y1); drawBranches(g, x1, y1, length * 0.75, angle + 30); drawBranches(g, x1, y1, length * 0.67, angle - 50); } } // end drawBranches
34
The view – the TreePanel Class
35 import javax.swing.JPanel; import java.awt.Graphics; import java.util.ArrayList; public class TreePanel extends JPanel { private final int HEIGHT; // height of frame private final int WIDTH; // width of frame private ArrayList<Tree> trees = new ArrayList<>(); private int time = 0; // in months //********************************************************** public TreePanel(int frameHeight, int frameWidth) this.HEIGHT = frameHeight; this.WIDTH = frameWidth; } // end constructor
35
The TreePanel Class − continued
36 //********************************************************** public void setTime(int time) { this.time = time; } // setTime public void addTree( int location, double trunkLength, int plantTime) trees.add(new Tree(location, plantTime, trunkLength)); } // end addTree public ArrayList<Tree> getTrees() return this.trees; } // end getTrees
36
The TreePanel Class − continued
37 public void paintComponent(Graphics g) { int location; // horizontal starting position of a tree String age; // age of a tree in years super.paintComponent(g); // draw a horizontal line representing surface of the earth: g.drawLine(25, HEIGHT - 75, WIDTH - 45, HEIGHT - 75); for (Tree tree : trees) // draw the current tree: location = tree.getStartX(); tree.drawBranches( g, location, HEIGHT - 75, tree.getTrunkLength(), 90); // write the age of the current tree: age = Integer.toString((time - tree.getStartTime()) / 12); g.drawString(age, location - 5, HEIGHT - 50); } } // end paintComponent } // end TreePanel class
37
The controller – the TreeDriver Class
38 import javax.swing.JFrame; import java.util.ArrayList; public class TreeDriver { private final int WIDTH = 625, HEIGHT = 400; private TreePanel panel = new TreePanel(HEIGHT, WIDTH); private int time = 0; //********************************************************** public TreeDriver() JFrame frame = new JFrame("Growing Trees"); frame.setSize(WIDTH, HEIGHT); frame.add(panel); frame.setVisible(true); } // end constructor
38
The TreeDriver Class − continued
39 public void simulate() throws Exception { ArrayList<Tree> trees = panel.getTrees(); boolean done = false; while(!done) switch (time) case 0: panel.addTree(400, 3, time); break; case 360: panel.addTree(100, 3, time); case 540: panel.addTree(300, 3, time); case 630: panel.addTree(200, 3, time); case 675: done = true; } // end switch
39
The TreeDriver Class − continued
40 Because it extends JPanel, our TreePanel class acquires this repaint method from the JPanel class, and repaint automatically calls TreePanel’s paintComponent method. panel.repaint(); time++; panel.setTime(time); for (Tree tree : trees) { tree.updateTrunkLength(); // to correspond to the new time } Thread.sleep(50); // throws an InterruptedExeption } // end while } // end simulate //********************************************************** public static void main(String[] args) throws Exception TreeDriver driver = new TreeDriver(); driver.simulate(); } // end main } // end TreeDriver class
40
Drawing Trees with a Fractal Algorithm
41 Here is the final display produced by the simulation:
41
Performance Analysis 42 One way to quantify the performance of an algorithm is to measure execution time. We described that in the previous chapter. Another way is to write a simple formula that approximates the dependence of time or space on data quantity. Time and space requirements are positively correlated, and we usually focus on time by approximating the number of computational steps as a function of data quantity. A typical for loop header gives the number of iterations. If each iteration takes the same amount of time, the time needed to iterate through the data set increases linearly with array length. If a loop includes another loop nested inside it, the total number of iterations is the number of iterations in the outer loop times the number of iterations of the inner loop.
42
Insertion Sort Method 43 public static void insertionSort(int[] list) { int itemToInsert; int j; for (int i=1; i<list.length; i++) itemToInsert = list[i]; for (j=i; j>0 && itemToInsert<list[j-1]; j--) list[j] = list[j-1]; // upshift previously sorted items } list[j] = itemToInsert; } // end for } // end insertionSort
43
Insertion Sort Method 44 The inner loop starts at the outer loop’s current index and iterates down through previously sorted items, shifting them upward as the iteration proceeds, until the item to insert is less than a previously sorted item. The portion of the array that is already sorted grows as the outer loop progresses. If the array is already sorted, itemToInsert < list[j-1] is always false, and the inner loop never executes. In this best case, the total number of steps is just the number of steps in the outer loop, or: minimumSteps = list.length – 1 If the array is initially in reverse order, itemsToInsert < list[j-1] is always true. In this worst case, the total number of steps is: maximumSteps = (list.length – 1) * list.length / 2 If the array is initially in random order, on average, the total number of steps is: averageSteps = (list.length – 1) * list.length / 4
44
Confounding Factors and Simplifications
45 The insertion-sort example shows that performance depends not only on the nature of the algorithm but also on the state of the data. Also, other things being equal, the time needed per operation typically decreases as the number of similar operations increases. For example, as an ArrayList’s length increases from 100 to 1,000 to 10,000, the average get and set time decreases from 340 ns to 174 ns to 122 ns, respectively. As a LinkedList’s length increases from 100 to 1,000 to 10,000, the average get and set time increases from 1,586 ns to 1,917 ns to 18,222 ns. This is less than the linear rate of increase we might expect. These hard-to-predict performance variations suggest that we should not expect high precision in performance analysis. For example, we could approximate the total time or space required to perform a task with something like this: time or space required ≈ a * f(n) + b where: n = total number of elements f(n) means “function of n”
45
Further Simplification and Big O Notation
46 Usually we simplify further by dropping a and b and writing: time or space required ≈ O(n) The right side is Big O notation. The O means “order of magnitude.” If the required time or space is the same for all n, we say “It’s O(1).” If the required time or space is directly proportional to n, we say “It’s O(n).” If the required time or space increases as the square of n, we say “It’s O(n2).” If the required time or space increases as the cube of n, we say, “It’s O(n3).” If the required time or space increases as log(n), we say, “It’s O(log n).” If the required time or space increases as n * log(n), we say, “It’s O(n log n).” For really some tough problems, the dependence might increase exponentially, like O(2n) or perhaps even O(nn). In such cases, we say the problem is intractable, which means it’s practically impossible to obtain exact answers.
46
Big O Dependencies 47 n O(1) O(log n) O(n) O(n log n) O(n2) O(n3) O(2n) O(nn) ,096 65, E19 , E5 1.8 E E115 ,048 65, E7 1.2 E77 #NUM! 1, ,024 10, E6 1.1 E9 #NUM! #NUM! 4, ,096 49, E7 6.9 E10 #NUM! #NUM! 16, , E5 2.7 E8 4.4 E12 #NUM! #NUM!
47
Big O Examples from Chapters 9 and 10
48 [§9.7] sequential search: O(n) [§9.7] binary search: O(log n) [§9.8] selection sort: O(n2) [§9.9] two-dimensional array fill: O(n2) [§10.2] List’s contains method: O(n) [§10.7] ArrayList’s get and set methods: O(1) [§10.7] LinkedList’s get and set methods: O(n) [§10.7] List’s indexed remove and add methods: O(n) [§10.7] ArrayDeque’s offer and poll methods: O(1) [§10.9] HashMap’s put, get, contains, and remove methods: O(1) [§10.9] TreeSet’s add and get methods: O(log n) [§10.7] HashMap’s put, get and contains methods: O(1)
48
Big O Examples from Chapter 11
49 [§11.4] Factorial: O(n) [§11.4] PrintReverseMessage: O(n) [§11.6] binary search: O(log n) [§11.7] merge sort: O(n log n) [§11.8] Towers of Hanoi: O(2n) [§11.9] drawBranches: O(n) , where n is number of branches [§11.10] insertion sort: O(n2), or O(n) if already sorted or nearly sorted
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.