Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
630
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

Within method call A, method calls B and E are made. The original method call has not yet completed, so its activation record remains on the stack. The first method call to be made from within A is method call B, so the activation record for method call B is pushed onto the stack on top of the activation record for method call A. Method call B must execute and complete before method call E is made. Within method call B, method calls C and D will be made. Method call C is made first, and its activation record is pushed onto the stack (part b of Fig. 15.9). Method call B has not yet finished, and its activation record is still on the method call stack. When method call C executes, it does not make any further method calls, but simply returns the value 1. When this method returns, its activation record is popped off the top of the stack. The method call at the top of the stack is now B, which continues to execute by performing method call D. The activation record for method call D is pushed onto the stack (part c of Fig. 15.9). Method call D completes without making any more method calls, and returns the value 0. The activation record for this method call is then popped off the stack. Now, both method calls made from within method call B have returned. Method call B continues to execute, returning the value 1. Method call B completes and its activation record is popped off the stack. At this point, the activation record for method call A is at the top of the stack and the method continues its execution. This method makes method call E, whose activation record is now pushed onto the stack (part d of Fig. 15.9). Method call E completes and returns the value 1. The activation record for this method call is popped off the stack, and once again method call A continues to execute. At this point, method call A will not be making any other method calls, and can finish its execution, returning the value 2 to A's caller (Fib(3) = 2). A's activation record is popped off the stack. Note that the current method executing is always the method whose activation record is at the top of the stack, and that the activation record for that method contains the values of the method's local variables.

[Page 754]

// iterative declaration of method factorial for ( long i = number; i >= 1; i-- )
result *= i;

[Page 754 (continued)]

15.6. Recursion vs. Iteration

In the preceding sections, we studied methods factorial and fibonacci, which can easily be implemented either recursively or iteratively. In this section, we compare the two approaches and discuss why the programmer might choose one approach over the other in a particular situation.

Both iteration and recursion are based on a control statement: Iteration uses a repetition statement (e.g., for, while or do...while), whereas recursion uses a selection statement (e.g., if, if...else or switch). Both iteration and recursion involve repetition: Iteration explicitly uses a repetition statement, whereas recursion achieves repetition through repeated method calls. Iteration and recursion each involve a termination test: Iteration terminates when the loop-continuation condition fails, whereas recursion terminates when a base case is reached. Iteration with counter-controlled repetition and recursion each gradually approach termination: Iteration keeps modifying a counter until the counter assumes a value that makes the loop-continuation condition fail, whereas recursion keeps producing simpler versions of the original problem until the base case is reached. Both iteration and recursion can occur infinitely: An infinite loop occurs with iteration if the loop-continuation test never becomes false, whereas infinite recursion occurs if the recursion step does not reduce the problem each time in a manner that converges on the base case, or if the base case is not tested.

[Page 755]

To illustrate the differences between iteration and recursion, let us examine an iterative solution to the factorial problem (Fig. 15.10Fig. 15.11). Note that a repetition statement is used (lines 1213 of Fig. 15.10) rather than the selection statement of the recursive solution (lines 912 of Fig. 15.3). Note that both solutions use a termination test. In the recursive solution, line 9 tests for the base case. In the iterative solution, line 12 tests the loop-continuation conditionif the test fails, the loop terminates. Finally, note that instead of producing simpler versions of the original problem, the iterative solution uses a counter that is modified until the loop-continuation condition becomes false.

Figure 15.10. Iterative factorial solution.

1// Fig. 15.10: FactorialCalculator.java

2// Iterative factorial method.

3

4public class FactorialCalculator

5{

6// recursive declaration of method factorial

7public long factorial( long number )

8{

9long result = 1;

10

11

12

13

14

15return result;

16} // end method factorial

18// output factorials for values 0-10

19public void displayFactorials()

20{

21// calculate the factorials of 0 through 10

22for ( int counter = 0; counter <= 10; counter++ )

23System.out.printf( "%d! = %d\n", counter, factorial( counter ) );

24} // end method displayFactorials

25} // end class FactorialCalculator

Figure 15.11. Testing the iterative factorial solution.

(This item is displayed on page 756 in the print version)

1// Fig. 15.11: FactorialTest.java

2// Testing the iterative 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

 

 

 

Recursion has many negatives. It repeatedly invokes the mechanism, and consequently the overhead, of method calls. This repetition can be expensive in terms of both processor time and memory space. Each recursive call causes another copy of the method (actually, only the method's variables, stored in the activation record) to be createdthis set of copies can consume considerable memory space. Since iteration occurs within a method, repeated method calls and extra memory assignment are avoided. So why choose recursion?

Software Engineering Observation 15.1

Any problem that can be solved recursively can also be solved iteratively (nonrecursively). A recursive approach is normally preferred over an iterative approach when the recursive approach more naturally mirrors the problem and results in a program that is easier to understand and debug. A recursive approach can often be implemented with fewer lines of code. Another reason to choose a recursive approach is that an iterative one might not be apparent.

[Page 756]

Performance Tip 15.2

Avoid using recursion in situations requiring high performance. Recursive calls take time and consume additional memory.

Common Programming Error 15.2

Accidentally having a nonrecursive method call itself either directly or indirectly through another method can cause infinite recursion.

[Page 756 (continued)]

15.7. String Permutations

The example in this section provides a more in-depth walkthrough of how to solve a recursive problem, especially one that does not involve a simple mathematical formula. The problem we analyze in this section is creating the permutations of a string of textall the different strings that can be created by rearranging the characters of the original string. Words created from these strings are known as anagrams. The permutations for the string "abc" are:

abc acb bac bca cab cba

[Page 757]

Such a program could come in handy if one wants to unscramble a string of characters, determine all the words that can be created from a string, or determine all the words that can be created from the characters associated with a telephone number.

We have already provided a few recursive solutions in this chapter, but we have not considered the flow of thought that results in a recursive solution. We do this now, in the context of string permutations. To begin, we look for a pattern that can be used to solve the problem. In the preceding list of permutations, note that two of the resulting permutations start with "a" ("abc" and "acb"), two with "b" ("bac" and "bca"), and two with "c" ("cab", "cba"). For each letter, permutations are provided that begin with that letter, followed by permutations of the remaining letters. If we were to begin with the letter "b", for example, we would have two permutations"bac" and "bca". These are determined by simply looking at the remaining two letters, "a" and "c", and seeing that there are only two permutations using these lettersnamely, "ac" and "ca". Note that we have now just determined all the permutations on the smaller string, "ac". To do this, we can use the same thought process as before, namely, determining all the permutations that start with the letter "a" followed by all the permutations that start with the letter "c". If we start with the letter "a", we have only one letter left, the letter "c". For a string with only one character, the string itself is the only permutation.

We now have the permutations of our substrings, but not the final permutations as shown above. To create these, we need to precede the permutations of the substrings with the characters that were removed to create the substring. For example, when determining the permutations of "bac", we divided the string into two strings ("b" and "ac"), and determined the permutations of the second string (in this case, "ac" and "ca"). We need our solution to precede "ac" and "ca" with the character that was removed to create this substring (namely, "b"). Our solution will need a way to keep track of these removed letters.

We have now found a way to break up our problem into two pieces: a single character from the original string, concatenated with all the permutations of the remaining charactersthe recursion step calculates all such permutations. The recursion step includes a recursive call for each letter in the string, with a different letter being used as the first letter of the permutation. Each call takes a different character from the string being permuted, and creates a substring of the remaining values, to be passed to the recursive call. For instance, if we are using the example of "abc", the first call to our recursive method results in three recursive callsone which determines all the permutations that begin with "a" (where the substring is "bc"), one which determines all the permutations that begin with "b" (where the substring is "ac") and one which determines all the permutations that begin with "c" (where the substring is

"ab").

We have now discovered the recursion step for our program (determining the permutations of the various substrings) and our base case (a string with one letter will always have only one permutation, the letter itself). The next step is to ensure that the base case will be reached. Because our recursion step always calls with a string that is one character shorter than the string before it, we can be sure that we will always reach a string of one characterthe base case. Note that the base case also handles the situation where the user enters an empty string (i.e., one whose length is 0).

// base case: if string to permute is length less than or equal to

Now that we have determined our base case, the recursion step and that the base case will always be reached, we present the code for our solution (Fig. 15.12). Method permuteString (lines 738) takes two arguments. The first, beginningString, contains the characters that were removed from the string in previous calls and now need to precede the permutations that are being created. The second argument, endingString, contains the string that needs to be permuted (we call it endingString because for our final results it will be displayed after beginningString). When the method is called with the original string, the first argument will be the empty string, as there are no characters from previous calls that need to be added to the solution. The base case occurs in lines 1213, when the string being permuted contains only one character. In this case, we simply print the characters from previous calls (beginningString) followed by the character in endingString.

[Page 758]

Figure 15.12. String permutations generated with a recursive method.

1

//

Fig. 15.12: Permutation.java

 

2

//

Recursive method to find all

permutations of a String.

3

 

 

 

4public class Permutation

5{

6// recursive declaration of method permuteString

7private void permuteString(

8String beginningString, String endingString )

9{

10

11// 1, just display this string concatenated with beginningString

12if ( endingString.length() <= 1 )

13System.out.println( beginningString + endingString );

14else // recursion step: permute endingString

15{

16// for each character in endingString

17

for (

int i

= 0; i < endingString.length();

i++

)

 

18

{

 

 

 

 

 

 

 

19

try

 

 

 

 

 

 

20

{

 

 

 

 

 

 

 

21

 

//

create new string to permute by eliminating the

22

 

//

character at

index i

 

 

 

23

 

String newString

= endingString.substring(

0, i

) +

24

 

 

endingString.substring( i + 1 );

 

 

 

25

 

 

 

 

 

 

 

 

26

 

//

recursive call with a new string to permute

 

27

 

//

and a beginning string to concatenate, which

 

28

 

//

includes the character at index i

 

 

 

29

 

permuteString( beginningString +

 

 

 

30

 

 

endingString.charAt( i ), newString );

 

 

31

}

//

end

try

 

 

 

 

32

catch

(

StringIndexOutOfBoundsException

exception

)

33

{

 

 

 

 

 

 

 

34

 

exception.printStackTrace();

 

 

 

35

}

//

end

catch

 

 

 

 

36} // end for

37} // end else

38} // end method permuteString

39} // end class Permutation

The recursion step occurs in lines 1437. The for statement makes a recursive call for each of the substrings. The current character being removed is the character returned from endingString.charAt( i ). Method charAt takes an integer argument and returns the character in the String at that index. As with arrays, the first element of a string is considered to be at position 0. The for statement iterates through each character in endingString, so that permutations will be created that begin with each letter

in endingString.

[Page 759]

To create the substring that needs to be permuted, the character at index i needs to be removed from

the string that will be passed in the recursive call. To remove a character, we concatenate two substringsthe first substring contains all the characters that occur before the character being removed, and the second substring contains the portion of the string that occurs after the character being removed. For instance, to remove the character "s" from the word "recursion," we create the new string by concatenating the first substring, "recur", with "ion", resulting in "recurion." Lines 2324 create this substring using String method substring. Class String provides two substring methods to return a new String object created by copying part of an existing String object. The call in line 23 passes method substring two integers (0 and i). The first argument specifies the starting index in the original string from which characters are copied. The second argument specifies the index one beyond the last character to be copied (i.e., copy up to, but not including, that index in the string). The substring returned contains copies of the specified range of characters from the original string. Therefore, the method call in line 23 returns all the characters from the beginning of endingString up to, but not including, the character at index i (the character we are trying to remove). If the arguments are outside the bounds of the string, the program generates a StringIndexOutOfBoundsException (handled in lines 3235).

The method call in line 24 uses the substring method that takes one integer argument (i + 1), which specifies the starting index in the original string from which characters are to be copied. The substring returned contains a copy of the characters from the starting index to the end of the string. Therefore, the method call in line 24 returns all the characters that occur after the character we are trying to remove. If the argument is outside the bounds of the string, the program generates a

StringIndexOutOfBoundsException (also handled in lines 3235).

Lines 2930 perform the recursive call. The first argument passed is beginningString concatenated with endingString.charAt( i ). This way we combine the characters isolated from previous calls (beginningString) with the character being isolated in this call. The second argument is newString, which is the substring to be permuted.

Figure 15.13 tests our recursive method. Line 9 creates a Scanner object to read a string in from the keyboard. Line 10 creates a Permutation object with which to call method permuteString. The string is read in at line 13 and passed to method permuteString in line 16. Note that this call provides an empty string as the first argument and the string to permute as the second argument. Because we have not yet removed any characters from the string, beginningString (the first argument) should be an empty string. Some programmers may want to define method permuteString as private, and create another public method that takes only the string to permute. This method could then call permuteString with the empty string as the first argument and the string to permute as the second argument. This would ensure that the user does not enter something other than the empty string as the first argument to method permuteString.

Figure 15.13. Testing the recursive method for permutations.

(This item is displayed on page 760 in the print version)

1

//

Fig. 15

.13:

PermutationTest.java

2

//

Testing

the

recursive method to permute strings.

3

import java.util.Scanner;

4

 

 

 

 

5public class PermutationTest

6{

7public static void main( String args[] )

8{

9Scanner scanner = new Scanner( System.in );

10Permutation permutationObject = new Permutation();

12System.out.print( "Enter a string: " );

13String input = scanner.nextLine(); // retrieve String to permute

15// permute String

16permutationObject.permuteString( "", input );

17} // end main

18} // end class PermutationTest

math maht mtah mtha

mhat mhta amth amht atmh athm ahmt ahtm tmah tmha tamh tahm thma tham hmat hmta hamt hatm htma htam

The permutations will be printed to the command prompt. Note that if a string is entered with repeated characters (e.g., "hello"), each character is treated on its own, resulting in repeated permutations. One way to handle this problem is to store permutations as they are createdwhen each new permutation is formed, check the previously created strings and add the new string only if it has not already appeared. Finally, note from the output that there are 24 permutations for the string "math". The number of unique permutations for a string with n unique characters is equal to the factorial of n (i.e., there are four characters in "math", resulting in 4! permutations, or 24 permutations).

[Page 760]

[Page 761]

15.8. Towers of Hanoi

In the preceding sections of this chapter, we studied methods that can be easily implemented both recursively and iteratively. In this section, we present a problem whose recursive solution demonstrates the elegance of recursion, and whose iterative solution may not be as apparent.

The Towers of Hanoi is one of the most famous classic problems every budding computer scientist must grapple with. Legend has it that in a temple in the Far East, priests are attempting to move a stack of golden disks from one diamond peg to another (Fig. 15.14). The initial stack has 64 disks threaded onto one peg and arranged from bottom to top by decreasing size. The priests are attempting to move the stack from one peg to another under the constraints that exactly one disk is moved at a time and at no time may a larger disk be placed above a smaller disk. Three pegs are provided, one being used for temporarily holding disks. Supposedly, the world will end when the priests complete their task, so there is little incentive for us to facilitate their efforts.

Figure 15.14. Towers of Hanoi for the case with four disks.

[View full size image]

Let us assume that the priests are attempting to move the disks from peg 1 to peg 3. We wish to develop an algorithm that prints the precise sequence of peg-to-peg disk transfers.

If we were to approach this problem with conventional methods, we would rapidly find ourselves hopelessly knotted up in managing the disks. Instead, attacking this problem with recursion in mind allows the steps to be simple. Moving n disks can be viewed in terms of moving only n - 1 disks (hence the recursion) as follows:

a.Move n - 1 disks from peg 1 to peg 2, using peg 3 as a temporary holding area.

b.Move the last disk (the largest) from peg 1 to peg 3.

c.Move the n - 1 disks from peg 2 to peg 3, using peg 1 as a temporary holding area.

The process ends when the last task involves moving n = 1 disk (i.e., the base case). This task is accomplished by simply moving the disk, without the need for a temporary holding area.

Figure 15.15 displays the precise instructions it will take to move the disks from the starting peg to the destination peg. In the constructor (lines 912), the number of disks to be moved (numDisks) is initialized.

Method solveTowers (lines 1534) solves the Towers of Hanoi puzzle given the total number of disks (in this case 3 ), the starting peg, the ending peg, and the temporary holding peg as parameters. The base case (lines 1923) occurs when only one disk needs to be moved from the starting peg to the ending peg. In the recursion step (lines 2733), line 27 moves disks - 1 disks from the first peg (sourcePeg) to the temporary holding peg (tempPeg). When all but one of the disks have been moved to the temporary peg, line 30 records the step to move one disk, the largest one, from the start peg to the destination peg. Line 33 finishes the rest of the moves by calling the method solveTowers to recursively move disks - 1 disks from the temporary peg (tempPeg) back to the destination peg (destinationPeg), this time using the first peg (sourcePeg) as the temporary peg.

[Page 762]

Figure 15.15. Towers of Hanoi solution with a recursive method.

1

// Fig. 15

.15: TowersOfHanoi.java

2

//

Program

solves the towers of Hanoi problem, and

3

//

demonstrates recursion.

4

 

 

 

5public class TowersOfHanoi

6{

7

int numDisks; // number of disks to move

8

 

9public TowersOfHanoi( int disks )

10{

11numDisks = disks;

12} // end TowersOfHanoi constructor

14// recusively move disks through towers

15public void solveTowers( int disks, int sourcePeg, int destinationPeg,

16int tempPeg )

17{

18// base case -- only one disk to move

19if ( disks == 1 )

20{

21System.out.printf( "\n%d --> %d", sourcePeg, destinationPeg );

22return;

23} // end if

24

25 // recursion step -- move disk to tempPeg, then to destinationPeg 26 // move ( disks - 1 ) disks from sourcePeg to tempPeg recursively 27 solveTowers( disks - 1, sourcePeg, tempPeg, destinationPeg );

28

29// move last disk from sourcePeg to destinationPeg

30System.out.printf( "\n%d --> %d", sourcePeg, destinationPeg );

32 // move ( disks - 1 ) disks from tempPeg to destinationPeg

33solveTowers( disks - 1, tempPeg, destinationPeg, sourcePeg );

34} // end method solveTowers

35} // end class TowersOfHanoi

Figure 15.16 tests our Towers of Hanoi solution. In the main method (lines 616), line 12 creates a TowersOfHanoi object, passing as a parameter the total number of disks to be moved from one peg to another. Line 15 calls the recursive solveTowers method which outputs the steps to the command prompt.

[Page 763]

Figure 15.16. Testing the Towers of Hanoi solution.

1

//

Fig. 15.16: TowersOfHanoiTest.java

2

//

Test

the

solution to the Towers of Hanoi problem.

3

 

 

 

 

4

public

class

TowersOfHanoiTest