
- •CONTENTS
- •1.1 Introduction
- •1.2 What Is a Computer?
- •1.3 Programs
- •1.4 Operating Systems
- •1.5 Java, World Wide Web, and Beyond
- •1.6 The Java Language Specification, API, JDK, and IDE
- •1.7 A Simple Java Program
- •1.8 Creating, Compiling, and Executing a Java Program
- •1.9 (GUI) Displaying Text in a Message Dialog Box
- •2.1 Introduction
- •2.2 Writing Simple Programs
- •2.3 Reading Input from the Console
- •2.4 Identifiers
- •2.5 Variables
- •2.7 Named Constants
- •2.8 Numeric Data Types and Operations
- •2.9 Problem: Displaying the Current Time
- •2.10 Shorthand Operators
- •2.11 Numeric Type Conversions
- •2.12 Problem: Computing Loan Payments
- •2.13 Character Data Type and Operations
- •2.14 Problem: Counting Monetary Units
- •2.15 The String Type
- •2.16 Programming Style and Documentation
- •2.17 Programming Errors
- •2.18 (GUI) Getting Input from Input Dialogs
- •3.1 Introduction
- •3.2 boolean Data Type
- •3.3 Problem: A Simple Math Learning Tool
- •3.4 if Statements
- •3.5 Problem: Guessing Birthdays
- •3.6 Two-Way if Statements
- •3.7 Nested if Statements
- •3.8 Common Errors in Selection Statements
- •3.9 Problem: An Improved Math Learning Tool
- •3.10 Problem: Computing Body Mass Index
- •3.11 Problem: Computing Taxes
- •3.12 Logical Operators
- •3.13 Problem: Determining Leap Year
- •3.14 Problem: Lottery
- •3.15 switch Statements
- •3.16 Conditional Expressions
- •3.17 Formatting Console Output
- •3.18 Operator Precedence and Associativity
- •3.19 (GUI) Confirmation Dialogs
- •4.1 Introduction
- •4.2 The while Loop
- •4.3 The do-while Loop
- •4.4 The for Loop
- •4.5 Which Loop to Use?
- •4.6 Nested Loops
- •4.7 Minimizing Numeric Errors
- •4.8 Case Studies
- •4.9 Keywords break and continue
- •4.10 (GUI) Controlling a Loop with a Confirmation Dialog
- •5.1 Introduction
- •5.2 Defining a Method
- •5.3 Calling a Method
- •5.4 void Method Example
- •5.5 Passing Parameters by Values
- •5.6 Modularizing Code
- •5.7 Problem: Converting Decimals to Hexadecimals
- •5.8 Overloading Methods
- •5.9 The Scope of Variables
- •5.10 The Math Class
- •5.11 Case Study: Generating Random Characters
- •5.12 Method Abstraction and Stepwise Refinement
- •6.1 Introduction
- •6.2 Array Basics
- •6.3 Problem: Lotto Numbers
- •6.4 Problem: Deck of Cards
- •6.5 Copying Arrays
- •6.6 Passing Arrays to Methods
- •6.7 Returning an Array from a Method
- •6.8 Variable-Length Argument Lists
- •6.9 Searching Arrays
- •6.10 Sorting Arrays
- •6.11 The Arrays Class
- •7.1 Introduction
- •7.2 Two-Dimensional Array Basics
- •7.3 Processing Two-Dimensional Arrays
- •7.4 Passing Two-Dimensional Arrays to Methods
- •7.5 Problem: Grading a Multiple-Choice Test
- •7.6 Problem: Finding a Closest Pair
- •7.7 Problem: Sudoku
- •7.8 Multidimensional Arrays
- •8.1 Introduction
- •8.2 Defining Classes for Objects
- •8.3 Example: Defining Classes and Creating Objects
- •8.4 Constructing Objects Using Constructors
- •8.5 Accessing Objects via Reference Variables
- •8.6 Using Classes from the Java Library
- •8.7 Static Variables, Constants, and Methods
- •8.8 Visibility Modifiers
- •8.9 Data Field Encapsulation
- •8.10 Passing Objects to Methods
- •8.11 Array of Objects
- •9.1 Introduction
- •9.2 The String Class
- •9.3 The Character Class
- •9.4 The StringBuilder/StringBuffer Class
- •9.5 Command-Line Arguments
- •9.6 The File Class
- •9.7 File Input and Output
- •9.8 (GUI) File Dialogs
- •10.1 Introduction
- •10.2 Immutable Objects and Classes
- •10.3 The Scope of Variables
- •10.4 The this Reference
- •10.5 Class Abstraction and Encapsulation
- •10.6 Object-Oriented Thinking
- •10.7 Object Composition
- •10.8 Designing the Course Class
- •10.9 Designing a Class for Stacks
- •10.10 Designing the GuessDate Class
- •10.11 Class Design Guidelines
- •11.1 Introduction
- •11.2 Superclasses and Subclasses
- •11.3 Using the super Keyword
- •11.4 Overriding Methods
- •11.5 Overriding vs. Overloading
- •11.6 The Object Class and Its toString() Method
- •11.7 Polymorphism
- •11.8 Dynamic Binding
- •11.9 Casting Objects and the instanceof Operator
- •11.11 The ArrayList Class
- •11.12 A Custom Stack Class
- •11.13 The protected Data and Methods
- •11.14 Preventing Extending and Overriding
- •12.1 Introduction
- •12.2 Swing vs. AWT
- •12.3 The Java GUI API
- •12.4 Frames
- •12.5 Layout Managers
- •12.6 Using Panels as Subcontainers
- •12.7 The Color Class
- •12.8 The Font Class
- •12.9 Common Features of Swing GUI Components
- •12.10 Image Icons
- •13.1 Introduction
- •13.2 Exception-Handling Overview
- •13.3 Exception-Handling Advantages
- •13.4 Exception Types
- •13.5 More on Exception Handling
- •13.6 The finally Clause
- •13.7 When to Use Exceptions
- •13.8 Rethrowing Exceptions
- •13.9 Chained Exceptions
- •13.10 Creating Custom Exception Classes
- •14.1 Introduction
- •14.2 Abstract Classes
- •14.3 Example: Calendar and GregorianCalendar
- •14.4 Interfaces
- •14.5 Example: The Comparable Interface
- •14.6 Example: The ActionListener Interface
- •14.7 Example: The Cloneable Interface
- •14.8 Interfaces vs. Abstract Classes
- •14.9 Processing Primitive Data Type Values as Objects
- •14.10 Sorting an Array of Objects
- •14.11 Automatic Conversion between Primitive Types and Wrapper Class Types
- •14.12 The BigInteger and BigDecimal Classes
- •14.13 Case Study: The Rational Class
- •15.1 Introduction
- •15.2 Graphical Coordinate Systems
- •15.3 The Graphics Class
- •15.4 Drawing Strings, Lines, Rectangles, and Ovals
- •15.5 Case Study: The FigurePanel Class
- •15.6 Drawing Arcs
- •15.7 Drawing Polygons and Polylines
- •15.8 Centering a String Using the FontMetrics Class
- •15.9 Case Study: The MessagePanel Class
- •15.10 Case Study: The StillClock Class
- •15.11 Displaying Images
- •15.12 Case Study: The ImageViewer Class
- •16.1 Introduction
- •16.2 Event and Event Source
- •16.3 Listeners, Registrations, and Handling Events
- •16.4 Inner Classes
- •16.5 Anonymous Class Listeners
- •16.6 Alternative Ways of Defining Listener Classes
- •16.7 Problem: Loan Calculator
- •16.8 Window Events
- •16.9 Listener Interface Adapters
- •16.10 Mouse Events
- •16.11 Key Events
- •16.12 Animation Using the Timer Class
- •17.1 Introduction
- •17.2 Buttons
- •17.3 Check Boxes
- •17.4 Radio Buttons
- •17.5 Labels
- •17.6 Text Fields
- •17.7 Text Areas
- •17.8 Combo Boxes
- •17.9 Lists
- •17.10 Scroll Bars
- •17.11 Sliders
- •17.12 Creating Multiple Windows
- •18.1 Introduction
- •18.2 Developing Applets
- •18.3 The HTML File and the <applet> Tag
- •18.4 Applet Security Restrictions
- •18.5 Enabling Applets to Run as Applications
- •18.6 Applet Life-Cycle Methods
- •18.7 Passing Strings to Applets
- •18.8 Case Study: Bouncing Ball
- •18.9 Case Study: TicTacToe
- •18.10 Locating Resources Using the URL Class
- •18.11 Playing Audio in Any Java Program
- •18.12 Case Study: Multimedia Animations
- •19.1 Introduction
- •19.2 How is I/O Handled in Java?
- •19.3 Text I/O vs. Binary I/O
- •19.4 Binary I/O Classes
- •19.5 Problem: Copying Files
- •19.6 Object I/O
- •19.7 Random-Access Files
- •20.1 Introduction
- •20.2 Problem: Computing Factorials
- •20.3 Problem: Computing Fibonacci Numbers
- •20.4 Problem Solving Using Recursion
- •20.5 Recursive Helper Methods
- •20.6 Problem: Finding the Directory Size
- •20.7 Problem: Towers of Hanoi
- •20.8 Problem: Fractals
- •20.9 Problem: Eight Queens
- •20.10 Recursion vs. Iteration
- •20.11 Tail Recursion
- •APPENDIXES
- •INDEX

20.4 Problem Solving Using Recursion 683
As shown in Figure 20.4, there are many duplicated recursive calls. For instance, fib(2) is called twice, fib(1) three times, and fib(0) twice. In general, computing fib(index) requires roughly twice as many recursive calls as does computing fib(index - 1). As you try larger index values, the number of calls substantially increases.
Besides the large number of recursive calls, the computer requires more time and space to run recursive methods.
Pedagogical Note
The recursive implementation of the fib method is very simple and straightforward, but not efficient. See Exercise 20.2 for an efficient solution using loops. Though it is not practical, the recursive fib method is a good example of how to write recursive methods.
20.4 Problem Solving Using Recursion
The preceding sections presented two classic recursion examples. All recursive methods have |
recursion characteristics |
the following characteristics: |
|
■ The method is implemented using an if-else or a switch statement that leads to |
if-else |
different cases. |
|
■ One or more base cases (the simplest case) are used to stop recursion. |
base cases |
■ Every recursive call reduces the original problem, bringing it increasingly closer to a |
reduction |
base case until it becomes that case. |
|
In general, to solve a problem using recursion, you break it into subproblems. Each subprob- |
|
lem is almost the same as the original problem but smaller in size. You can apply the same |
|
approach to each subproblem to solve it recursively. |
|
Let us consider the simple problem of printing a message n times. You can break the problem |
|
into two subproblems: one is to print the message one time and the other is to print it n - 1 times. |
|
The second problem is the same as the original problem but smaller in size. The base case for the |
|
problem is n == 0. You can solve this problem using recursion as follows: |
|
public static void nPrintln(String message, int times) { |
|
if (times >= 1) { |
|
System.out.println(message); |
|
nPrintln(message, times - 1); |
recursive call |
} // The base case is times == 0 |
|
} |
|
Note that the fib method in the preceding example returns a value to its caller, but the |
|
nPrintln method is void and does not. |
|
If you think recursively, you can use recursion to solve many of the problems presented in |
think recursively |
earlier chapters of this book. Consider the palindrome problem in Listing 9.1. Recall that a |
|
string is a palindrome if it reads the same from the left and from the right. For example, mom |
|
and dad are palindromes, but uncle and aunt are not. The problem of checking whether a |
|
string is a palindrome can be divided into two subproblems: |
|
■ Check whether the first character and the last character of the string are equal. |
|
■ Ignore the two end characters and check whether the rest of the substring is a |
|
palindrome. |
|
The second subproblem is the same as the original problem but smaller in size. There are two |
|
base cases: (1) the two end characters are not same; (2) the string size is 0 or 1. In case 1, the |
|
string is not a palindrome; and in case 2, the string is a palindrome. The recursive method for |
|
this problem can be implemented as shown in Listing 20.3. |
|

684 Chapter 20 |
|
Recursion |
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
LISTING 20.3 |
RecursivePalindromeUsingSubstring.java |
||||||||||
|
1 |
public class RecursivePalindromeUsingSubstring { |
||||||||||||||
method header |
2 |
|
public static boolean isPalindrome(String s) { |
|
|
|||||||||||
base case |
3 |
|
if (s.length() <= 1) // Base case |
|||||||||||||
|
4 |
|
return true; |
|||||||||||||
base case |
5 |
|
else if (s.charAt(0) != s.charAt(s.length() - 1)) // Base case |
|||||||||||||
|
6 |
|
return false; |
|||||||||||||
|
7 |
|
else |
|
|
|
|
|
|
|
|
|||||
recursive call |
8 |
|
return |
isPalindrome(s.substring(1, s.length() - 1)) |
; |
|
||||||||||
|
9 |
} |
|
|
|
|
|
|
|
|
|
|||||
|
10 |
|
|
|
|
|
|
|
|
|
|
|
||||
|
11 |
|
public static void main(String[] args) { |
|||||||||||||
|
12 |
|
System.out.println("Is moon a palindrome? " |
|||||||||||||
|
13 |
|
+ isPalindrome("moon")); |
|||||||||||||
|
14 |
|
System.out.println("Is noon a palindrome? " |
|||||||||||||
|
15 |
|
+ isPalindrome("noon")); |
|||||||||||||
|
16 |
|
System.out.println("Is a a palindrome? " + isPalindrome("a")); |
|||||||||||||
|
17 |
|
System.out.println("Is aba a palindrome? " + |
|||||||||||||
|
18 |
|
isPalindrome("aba")); |
|||||||||||||
|
19 |
|
System.out.println("Is ab a palindrome? " + isPalindrome("ab")); |
|||||||||||||
|
20 |
} |
|
|
|
|
|
|
|
|
|
|||||
|
21 |
} |
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
Is moon a palindrome? false |
|||||||||||
|
|
|
|
|
Is noon a palindrome? true |
|||||||||||
|
|
|
|
|||||||||||||
|
|
|
|
|||||||||||||
|
|
|
|
|
Is a a palindrome? true |
|||||||||||
|
|
|
|
|
Is aba a palindrome? true |
|||||||||||
|
|
|
|
|
Is ab a palindrome? false |
|||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
The substring method in line 8 creates a new string that is the same as the original string |
|||||||||||
|
|
|
|
|
except without the first and last characters. Checking whether a string is a palindrome is |
|||||||||||
|
|
|
|
|
equivalent to checking whether the substring is a palindrome if the two end characters in the |
|||||||||||
|
|
|
|
|
original string are the same. |
|||||||||||
|
|
|
|
|
20.5 Recursive Helper Methods |
|||||||||||
|
|
|
|
|
The preceding recursive isPalindrome method is not efficient, because it creates a new |
|||||||||||
|
|
|
|
|
string for every recursive call. To avoid creating new strings, you can use the low and high |
|||||||||||
|
|
|
|
|
indices to indicate the range of the substring. These two indices must be passed to the recur- |
|||||||||||
|
|
|
|
|
sive method. Since the original method is isPalindrome(String s), you have to create a |
|||||||||||
|
|
|
|
|
new method isPalindrome(String s, int low, int high) to accept additional infor- |
|||||||||||
|
|
|
|
|
mation on the string, as shown in Listing 20.4. |
|||||||||||
|
|
|
|
|
LISTING 20.4 |
RecursivePalindrome.java |
||||||||||
|
1 |
public class RecursivePalindrome { |
||||||||||||||
|
2 |
|
public static boolean isPalindrome(String s) { |
|
||||||||||||
|
3 |
|
return isPalindrome(s, 0, s.length() - 1); |
|||||||||||||
|
4 |
} |
|
|
|
|
|
|
|
|
|
|||||
|
5 |
|
|
|
|
|
|
|
|
|
|
|
||||
helper method |
6 |
|
public static boolean isPalindrome(String s, int low, int high) { |
|
||||||||||||
base case |
7 |
|
if |
(high <= low) |
// Base case |
|||||||||||
|
8 |
|
return true; |
|||||||||||||
base case |
9 |
|
else if |
(s.charAt(low) != s.charAt(high)) |
// Base case |
|||||||||||
|
10 |
|
return false; |

20.5 Recursive Helper Methods 685
11else
12return isPalindrome(s, low + 1, high - 1);
13}
14
15public static void main(String[] args) {
16System.out.println("Is moon a palindrome? "
17+ isPalindrome("moon"));
18System.out.println("Is noon a palindrome? "
19+ isPalindrome("noon"));
20System.out.println("Is a a palindrome? " + isPalindrome("a"));
21System.out.println("Is aba a palindrome? " + isPalindrome("aba"));
22System.out.println("Is ab a palindrome? " + isPalindrome("ab"));
23}
24}
Two overloaded isPalindrome methods are defined. The first, isPalindrome(String s), checks whether a string is a palindrome, and the second, isPalindrome(String s, int low, int high), checks whether a substring s(low..high) is a palindrome. The first method passes the string s with low = 0 and high = s.length() – 1 to the second method. The second method can be invoked recursively to check a palindrome in an ever-shrinking substring. It is a common design technique in recursive programming to define a second method
that receives additional parameters. Such a method is known as a recursive helper method. recursive helper method Helper methods are very useful in designing recursive solutions for problems involving
strings and arrays. The sections that follow give two more examples.
20.5.1Selection Sort
Selection sort was introduced in §6.10.1, “Selection Sort.” Recall that it finds the smallest number in the list and places it first. It then finds the smallest number remaining and places it after the first, and so on until the remaining list contains only a single number. The problem can be divided into two subproblems:
■Find the smallest number in the list and swap it with the first number.
■Ignore the first number and sort the remaining smaller list recursively.
The base case is that the list contains only one number. Listing 20.5 gives the recursive sort method.
LISTING 20.5 RecursiveSelectionSort.java
1 public class RecursiveSelectionSort {
2public static void sort(double[] list) {
3sort(list, 0, list.length - 1); // Sort the entire list
4 |
} |
5 |
|
6 |
public static void sort(double[] list, int low, int high) { |
7if (low < high) {
8 // Find the smallest number and its index in list(low .. high) 9 int indexOfMin = low;
10double min = list[low];
11for (int i = low + 1; i <= high; i++) {
12if (list[i] < min) {
13min = list[i];
14indexOfMin = i;
15}
16}
17
18 // Swap the smallest in list(low .. high) with list(low)
helper method base case

686 Chapter 20 |
Recursion |
|||
|
19 |
|
list[indexOfMin] = list[low]; |
|
|
20 |
|
list[low] = min; |
|
|
21 |
|
|
|
|
22 |
|
// Sort the remaining list(low+1 .. high) |
|
|
23 |
|
sort(list, low + 1, high); |
|
recursive call |
24 |
} |
|
|
|
25 |
} |
|
|
|
26 |
} |
|
|
Two overloaded sort methods are defined. The first method, sort(double[] list), sorts an array in list[0..list.length - 1] and the second method sort(double[] list, int low, int high) sorts an array in list[low..high]. The second method can be invoked recursively to sort an ever-shrinking subarray.
Video Note
Binary search
20.5.2Binary Search
Binary search was introduced in §6.9.2. For binary search to work, the elements in the array must already be ordered. The binary search first compares the key with the element in the middle of the array. Consider the following three cases:
■Case 1: If the key is less than the middle element, recursively search the key in the first half of the array.
■Case 2: If the key is equal to the middle element, the search ends with a match.
■Case 3: If the key is greater than the middle element, recursively search the key in the second half of the array.
Case 1 and Case 3 reduce the search to a smaller list. Case 2 is a base case when there is a match. Another base case is that the search is exhausted without a match. Listing 20.6 gives a clear, simple solution for the binary search problem using recursion.
LISTING 20.6 Recursive Binary Search Method
|
1 |
public class RecursiveBinarySearch { |
|||||||||
|
2 |
|
public static int recursiveBinarySearch(int[] list, int key) { |
|
|||||||
|
3 |
|
int low = 0; |
||||||||
|
4 |
|
int high = list.length - 1; |
||||||||
|
5 |
|
return recursiveBinarySearch(list, key, low, high); |
||||||||
|
6 |
} |
|
|
|
|
|
|
|
|
|
|
7 |
|
|
|
|
|
|
|
|
|
|
|
8 |
|
public static int recursiveBinarySearch(int[] list, int key, |
|
|
||||||
helper method |
9 |
|
|
int low, int high) { |
|
|
|
||||
|
10 |
|
if |
(low > high) |
// The list has been exhausted without a match |
||||||
base case |
11 |
|
return -low - 1; |
||||||||
|
12 |
|
|
|
|
|
|
|
|
|
|
|
13 |
|
int mid = (low + high) / 2; |
||||||||
|
14 |
|
if (key < list[mid]) |
||||||||
|
15 |
|
|
return recursiveBinarySearch(list, key, low, mid - 1); |
|
|
|
||||
recursive call |
16 |
|
else if (key == list[mid]) |
||||||||
|
17 |
|
return mid; |
||||||||
base case |
18 |
|
else |
||||||||
|
19 |
|
|
return recursiveBinarySearch(list, key, mid + 1, high); |
|
|
|
||||
recursive call |
20 |
} |
|
|
|
|
|
|
|
|
|
|
21 |
} |
|
|
|
|
|
|
|
|
|
The first method finds a key in the whole list. The second method finds a key in the list with index from low to high.