AhmadLang / Java, How To Program, 2004
.pdf
15.10 Recursive Backtracking
15.11 Wrap-Up
15.12 Internet and Web Resources
Summary
Terminology
Self-Review Exercises
Answers to Self-Review Exercises Exercises
[Page 745 (continued)]
15.1. Introduction
The programs we have discussed thus far are generally structured as methods that call one another in a disciplined, hierarchical manner. For some problems, however, it is useful to have a method call itself. Such a method is known as a recursive method. A recursive method can be called either directly, or indirectly through another method. Recursion is an important topic discussed at length in upper-level computer science courses. In this chapter, we consider recursion conceptually, then present several programs containing recursive methods. Figure 15.1 summarizes the recursion examples and exercises in the book.
Figure 15.1. Summary of the 32 recursion examples and exercises in this text.
(This item is displayed on page 746 in the print version)
Chapter |
Recursion examples and exercises in this book |
15Factorial Method (Fig. 15.3 and Fig. 15.4) Fibonacci Method (Fig. 15.5 and Fig. 15.6) String Permutations (Fig. 15.12 and Fig. 15.13) Towers of Hanoi (Fig. 15.15 and Fig. 15.16) Fractals (Fig. 15.23 and Fig. 15.24)
What Does This Code Do? (Exercise 15.7, Exercise 15.12 and Exercise 15.13)
Find the Error in the Following Code (Exercise 15.8) Raising an Integer to an Integer Power (Exercise 15.9) Visualizing Recursion (Exercise 15.10)
Greatest Common Divisor (Exercise 15.11)
Determine Whether a String Is a Palindrome (Exercise 15.14) Eight Queens (Exercise 15.15)
Print an Array (Exercise 15.16)
Print an Array Backward (Exercise 15.17) Minimum Value in an Array (Exercise 15.18) Star Fractal (Exercise 15.19)
Maze Traversal Using Recursive Backtracking (Exercise 15.20) Generating Mazes Randomly (Exercise 15.21)
Mazes of Any Size (Exercise 15.22)
Time Needed to Calculate a Fibonacci Number (Exercise 15.23)
16Merge Sort (Fig. 16.10 and Fig. 16.11) Linear Search (Exercise 16.8)
Binary Search (Exercise 16.9) Quicksort (Exercise 16.10)
17Binary-Tree Insert (Fig. 17.17)
Preorder Traversal of a Binary Tree (Fig. 17.17) Inorder Traversal of a Binary Tree (Fig. 17.17) Postorder Traversal of a Binary Tree (Fig. 17.17) Print a Linked List Backward (Exercise 17.20) Search a Linked List (Exercise 17.21)
[Page 745 (continued)]
15.2. Recursion Concepts
Recursive problem-solving approaches have a number of elements in common. When a recursive method is called to solve a problem, the method actually is capable of solving only the simplest case(s), or base case(s). If the method is called with a base case, the method returns a result. If the method is called with a more complex problem, the method typically divides the problem into two conceptual piecesa piece that the method knows how to do and a piece that the method does not know how to do. To make recursion feasible, the latter piece must resemble the original problem, but be a slightly simpler or smaller version of it. Because this new problem looks like the original problem, so the method calls a fresh copy of itself to work on the smaller problemthis is referred to as a recursive call and is also called the recursion step. The recursion step normally includes a return statement, because its result will be combined with the portion of the problem the method knew how to solve to form a result that will be passed back to the original caller. This concept of separating the problem into two smaller portions is a form of the divide-and-conquer approach introduced at the beginning of Chapter 6.
The recursion step executes while the original call to the method is still active (i.e., while it has not finished executing). The recursion step can result in many more recursive calls as the method divides each new subproblem into two conceptual pieces. For the recursion to eventually terminate, each time the method calls itself with a simpler version of the original problem, the sequence of smaller and smaller problems must converge on a base case. At that point, the method recognizes the base case and returns a result to the previous copy of the method. A sequence of returns ensues until the original method call returns the final result to the caller.
[Page 746]
A recursive method may call another method, which may in turn make a call back to the recursive method. Such a process is known as an indirect recursive call or indirect recursion. For example, method A calls method B, which makes a call back to method A. This is still considered recursion, because the second call to method A is made while the first call to method A is activethat is, the first call to method A has not yet finished executing (because it is waiting on method B to return a result to i) and has not returned to method A's original caller.
[Page 747]
To better understand the concept of recursion, let us look at an example of recursion that is quite common to computer usersthe recursive definition of a directory on a computer. A computer normally stores related files in a directory. A directory can be empty, can contain files and/or can contain other directories (usually referred to as subdirectories). Each of these subdirectories, in turn, may also contain both files and directories. If we wanted to list each file in a directory (including all the files in the directory's subdirectories), we would need to create a method that first lists the initial directory's files, then makes recursive calls to list the files in each of that directory's subdirectories. The base case would occur when a directory is reached that does not contain any subdirectories. At this point, all the files in the original directory have been listed and no further recursive calls need to be made.
[Page 747 (continued)]
15.3. Example Using Recursion: Factorials
Let us write a recursive program to perform a popular mathematical calculation. Consider the factorial of a positive integer n, written n! (and pronounced "n factorial"), which is the product
with 1! equal to 1 and 0! defined to be 1. For example, 5! is the product 5 · 4 · 3 · 2 · 1, which is equal to 120.
The factorial of integer number (where number
0) can be calculated iteratively (non-recursively) using a for statement as follows:
factorial = 1;
for ( int counter = number; counter >= 1; counter-- ) factorial *= counter;
A recursive declaration of the factorial method is arrived at by observing the following relationship:
For example, 5! is clearly equal to 5 · 4!, as is shown by the following equations:
The evaluation of 5! would proceed as shown in Fig. 15.2. Figure 15.2(a) shows how the succession of recursive calls proceeds until 1! (the base case) is evaluated to be 1, which terminates the recursion. Figure 15.2(b) shows the values returned from each recursive call to its caller until the final value is calculated and returned.
Figure 15.2. Recursive evaluation of 5!.
(This item is displayed on page 748 in the print version)
[View full size image]
that it does not converge on the base case can cause a logic error known as infinite recursion, where recursive calls are continuously made until memory has been exhausted. This error is analogous to the problem of an infinite loop in an iterative (nonrecursive) solution.
Method displayFactorials (lines 1621) displays the factorials of 010. The call to method factorial occurs in line 20. Method factorial receives a parameter of type long and returns a result of type long. Figure 15.4 tests our factorial and displayFactorials methods by calling displayFactorials (line 10). As can be seen from the output of Fig. 15.4, factorial values become large quickly. We use type long (which can represent relatively large integers) so the program can calculate factorials greater than 12!. Unfortunately, the factorial method produces large values so quickly that factorial values soon exceed the maximum value that can be stored even in a long variable.
Figure 15.4. Testing the factorial method.
1// Fig. 15.4: FactorialTest.java
2// Testing the recursive factorial method.
4public class FactorialTest
5{
6// calculate factorials of 0-10
7public static void main( String args[] )
8{
9FactorialCalculator factorialCalculator = new FactorialCalculator();
10factorialCalculator.displayFactorials();
11} // end main
12} // end class FactorialTest
0! |
= |
1 |
1! |
= |
1 |
2! |
= |
2 |
3! |
= |
6 |
4! |
= |
24 |
5! |
= |
120 |
6! |
= |
720 |
7! |
= |
5040 |
8! |
= |
40320 |
9! |
= |
362880 |
10! |
= |
3628800 |
|
|
|
Due to the limitations of integral types, float or double variables may ultimately be needed to calculate factorials of larger numbers. This points to a weakness in most programming languagesnamely, that the languages are not easily extended to handle the unique application requirements. As we saw in Chapter 9, Java is an extensible language that allows us to create arbitrarily large integers if we wish. In fact, package java.math provides classes BigInteger and BigDecimal explicitly for arbitrary precision mathematical calculations that cannot be performed with primitive types. For more information on these classes visit, java.sun.com/j2se/5.0/docs/api/java/math/BigInteger.html and java.sun.com/j2se/5.0/docs/api/java/math/BigDecimal.html, respectively.
3
4public class FibonacciTest
5{
6public static void main( String args[] )
7{
8FibonacciCalculator fibonacciCalculator = new FibonacciCalculator();
9fibonacciCalculator.displayFibonacci();
10} // end main
11} // end class FibonacciTest
Fibonacci of 0 is: 0
Fibonacci of 1 is: 1
Fibonacci of 2 is: 1
Fibonacci of 3 is: 2
Fibonacci of 4 is: 3
Fibonacci of 5 is: 5
Fibonacci of 6 is: 8
Fibonacci of 7 is: 13
Fibonacci of 8 is: 21
Fibonacci of 9 is: 34
Fibonacci of 10 is: 55
[Page 751]
The call to method fibonacci (line 19 of Fig. 15.5) from displayFibonacci is not a recursive call, but all subsequent calls to fibonacci performed from the body of fibonacci (line 12 of Fig. 15.5) are recursive, because at that point the calls are initiated by method fibonacci itself. Each time fibonacci is called, it immediately tests for the base casesnumber equal to 0 or number equal to 1 (line 9). If this condition is true, fibonacci simply returns number because fibonacci( 0 ) is 0, and fibonacci( 1 ) is 1. Interestingly, if number is greater than 1, the recursion step generates two recursive calls (line 12), each for a slightly simpler problem than the original call to fibonacci.
Figure 15.7 shows how method fibonacci evaluates fibonacci( 3 ). Note that at the bottom of the figure, we are left with the values 1, 0 and 1the results of evaluating the base cases. The first two return values (from left to right), 1 and 0, are returned as the values for the calls fibonacci( 1 ) and fibonacci( 0 ). The sum 1 plus 0 is returned as the value of fibonacci( 2 ). This is added to the result
(1) of the call to fibonacci( 1 ), producing the value 2. This final value is then returned as the value of fibonacci( 3 ).
Figure 15.7. Set of recursive calls for fibonacci ( 3 ).
(This item is displayed on page 752 in the print version)
[View full size image]
Figure 15.7 raises some interesting issues about the order in which Java compilers evaluate the operands of operators. This order is different from the order in which operators are applied to their operandsnamely, the order dictated by the rules of operator precedence. From Figure 15.7, it appears that while fibonacci( 3 ) is being evaluated, two recursive calls will be madefibonacci( 2 ) and fibonacci( 1 ). But in what order will these calls be made? The Java language specifies that the order of evaluation of the operands is from left to right. Thus, the call fibonacci( 2 ) is made first and the call fibonacci( 1 ) is made second.
[Page 752]
A word of caution is in order about recursive programs like the one we use here to generate Fibonacci numbers. Each invocation of the fibonacci method that does not match one of the base cases (0 or 1) results in two more recursive calls to the fibonacci method. Hence, this set of recursive calls rapidly gets out of hand. Calculating the Fibonacci value of 20 with the program in Fig. 15.5 requires 21,891 calls to the fibonacci method; calculating the Fibonacci value of 30 requires 2,692,537 calls! As you try to calculate larger Fibonacci values, you will notice that each consecutive Fibonacci number you use the application to calculate results in a substantial increase in calculation time and in the number of calls to the fibonacci method. For example, the Fibonacci value of 31 requires 4,356,617 calls, and the Fibonacci value of 32 requires 7,049,155 calls! As you can see, the number of calls to fibonacci increases quickly1,664,080 additional calls between Fibonacci values of 30 and 31 and 2,692,538 additional calls between Fibonacci values of 31 and 32! The difference in the number of calls made between Fibonacci values of 31 and 32 is more than 1.5 times the number of calls for Fibonacci values between 30 and 31. Problems of this nature can humble even the world's most powerful computers. [Note: In the field of complexity theory, computer scientists study how hard algorithms work to complete their tasks. Complexity issues are discussed in detail in the upper-level computer science curriculum course generally called "Algorithms." We introduce various complexity issues in Chapter 16, Searching and Sorting.] In the exercises, you will be asked to enhance the Fibonacci program of Fig. 15.5 such that it calculates the approximate amount of time required to perform the calculation. For this purpose, you will call static System method currentTimeMillis, which takes no arguments and returns the computer's current time in milliseconds.
Performance Tip 15.1
Avoid Fibonacci-style recursive programs, because they result in an exponential "explosion" of method calls.
[Page 753]
15.5. Recursion and the Method Call Stack
In Chapter 6, the stack data structure was introduced in the context of understanding how Java performs method calls. We discussed both the method call stack (also known as the program execution stack) and activation records. In this section, we will use these concepts to demonstrate how the program execution stack handles recursive method calls.
Let us begin by returning to the Fibonacci examplespecifically, calling method fibonacci with the value 3, as in Fig. 15.7. To more easily demonstrate in what order the activation records for the method calls are placed on the stack, we have lettered the method calls, as shown in Fig. 15.8.
Figure 15.8. Method calls made within the call fibonacci ( 3 ).
[View full size image]
When the first method call (A) is made, an activation record is pushed onto the program execution stack which contains the value of the local variable number (3, in this case). The program execution stack, including the activation record for method call A, is illustrated in part a of Fig. 15.9. [Note: In an actual computer, the program execution stack and its activation records would be more complex than in Fig. 15.9, containing such information as where the method call is to return to when it has completed execution. We use a simplified stack to demonstrate how the program execution stack works.]
Figure 15.9. Method calls on the program execution stack.
(This item is displayed on page 754 in the print version)
[View full size image]
