JMU JMU - Department of Computer Science
Help Tools
Lab: Gaining Experience Tracing Recursive Methods


Instructions: Answer the following questions one at a time. After answering each question, check your answer (by clicking on the check-mark icon if it is available) before proceeding to the next question.

Getting Ready: Before going any further, you should:

  1. Depending on your development environment, create either a directory or a project for this lab.
  2. Setup your development environment.
  3. Download the following files:
    to an appropriate directory/folder. (In most browsers/OSs, the easiest way to do this is by right-clicking/control-clicking on each of the links above and then selecting Save as... or Save link as....)
  4. Add the appropriate files you downloaded to the directory/project you created for this lab.

    .java Files: In most IDEs, .java files (i.e., source files) can just be copied into the project.

    .class and .jar Files: In some IDEs it is easier to use .class files and in others it is easier to use a .jar file that contains the .class files. Hence, you have been provided with both. See the course "Help" page on your IDE for more information.

    Resources: In some IDEs, resources (e.g., images, data files) need to be in a special directory whereas in others they need to be in the working directory. See the course "Help" page on your IDE for more information.

1. Tracing Method Calls: This part of the lab will help you understand how to trace the execution of method calls and why it is more difficult to trace recursive method calls.
  1. Open Analytic.java and GeometryDriver.java.
  2. Trace (by hand) the execution of GeometryDriver assuming it is executed as follows: java GeometryDriver 400.
  3. What would be output?


    A square with area 400.0 has a perimeter of 80.0
        
    Expand
  4. How many times is Analytic.findPerimeterOfSquare() called?


    1
    Expand
  5. How many "instances"/"copies" of the Analytic.findPerimeterOfSquare() method (and, hence, the local variables used in it) are being used at any point in time?


    1
    Expand
  6. How often is a value assigned to each variable?


    Once.
    Expand
  7. Why does this make it relatively easy to trace the execution of Analytic.findPerimeterOfSquare()?


    You only need to keep track of one "instance"/"copy" of the local variables and once a value is assigned to them the value doesn't change. Hence, you can trace the execution of this method without an eraser.
    Expand
  8. Close Analytic.java and open Iterative.java.
  9. In GeometryDriver.java, change perimeter = Analytic.findPerimeterOfSquare(area); to perimeter = Iterative.findPerimeterOfSquare(area);
  10. Trace (by hand) the execution of GeometryDriver assuming it is executed as follows: java GeometryDriver 400.
  11. What would be output?


    A square with area 400.0 has a perimeter of 80.0
        
    Expand
  12. How many times is Iterative.findPerimeterOfSquare() called?


    1
    Expand
  13. How many "instances"/"copies" of the Iterative.findPerimeterOfSquare() method (and, hence, the local variables used in it) are being used at any point in time?


    1
    Expand
  14. How often is a value assigned to each variable?


    A value is assigned to width one time. A value is assigned to perimeter five times.
    Expand
  15. Why does this make it a little more difficult to trace the execution of Iterative.findPerimeterOfSquare()?


    It is a little more difficult to trace this method because the value of perimeter changes. So, you either have to erase old values (if you don't care about the history) or cross them out (if you want to keep track of how it changed over time).
    Expand
  16. Close Iterative.java and open Recursive.java.
  17. In GeometryDriver.java, change perimeter = Iterative.findPerimeterOfSquare(area); to perimeter = Recursive.findPerimeterOfSquare(area);
  18. Using one piece of paper, trace (by hand) the execution of GeometryDriver assuming it is executed as follows: java GeometryDriver 400.
  19. What would be output?


    A square with area 400.0 has a perimeter of 80.0
        
    Expand
  20. How many times is Recursive.findPerimeterOfSquare() called?


    1
    Expand
  21. How many times is Recursive.increaseBy() called?


    4
    Expand
  22. How many "instances"/"copies" of the Recursive.increaseBy() method (and, hence, the local variables used in it) are being used at any point in time?


    Initially 1, then 2, then 3, then 4.
    Expand
  23. Why is it more difficult to trace the execution of Recursive.increaseBy() (than either the analytic algorithm or the iterative algorithm)?


    Because there are multiple "instances"/"copies" of the method and local variables and you have to remember where each copy was called from and will return to. This makes it very hard keep track of everything.
    Expand
2. Tracing Simple Recursive Methods: This part of the lab will help you understand how to trace the execution of a simple recursive method.
  1. Print four "cards" like the following for increaseBy():

    increaseBy_card.png

    and make one card for main() and one card for findPerimeterOfSquare(). (Note: If you're working on this during a scheduled lab period, pre-printed cards are available for increaseBy().)

  2. Using the cards, trace (by hand) the execution of GeometryDriver assuming it is executed as follows: java GeometryDriver 400. Specifically, each time a method is called, write the parameters on a card, and put the card on a stack (that will initially be empty). Then, trace that method.

    When the method returns, write down the return value, take the card off of the stack, and replace the method call (on the next card) with the return value.

  3. What would be output?


    A square with area 400.0 has a perimeter of 80.0
        
    Expand
  4. Using the same technique, trace the execution of ExtreminatorDriver. (Note: Cards are available.)
  5. What would be output?


        The answer is: 6
        
    Expand
3. Tracing Complicated Recursive Methods: This part of the lab will help you understand how to trace the execution of more complicated recursive methods.
  1. Open the Searcher class.
  2. Print cards for the search() method. (Note: If you're working on this during a scheduled lab period, pre-printed cards are available.)
  3. Trace the execution of the search() method in the Searcher class when it is passed 0 as first, 4 as last, 18 as target, and the array {1, 3, 6, 7, 10} as data.
  4. What answer did you get? (Note: You should know the correct answer. If you didn't get the correct answer try again.)


    -1 (indicating that the target is not in the array named data).
    Expand
  5. Trace the execution of the search() method in the Searcher class when it is passed 0 as first, 4 as last, 7 as target, and the array {1, 3, 6, 7, 10} as data.
  6. What answer did you get? (Note: You should know the correct answer. If you didn't get the correct answer try again.)


    3 (i.e., the index of the element containing the value 7).
    Expand
4. Tracing Recursive Methods to Improve Them: This part of the lab will help you understand how tracing recursive methods can provide you with information about how to improve them.
  1. Trace the execution of the search() method in the Searcher class when it is passed 0 as first, 3 as last, 1 as target, and the array {1, 3, 6, 7} as data.
  2. What answer did you get? (Note: You should know the correct answer. If you didn't get the correct answer try again.)


    0 (i.e., the index of the element containing the value 1).
    Expand
  3. What is inefficient (not confusing/complicated) about this implementation of the search() method?


    Even if the call to search() that is passed first and middle finds the target, it still calls search() and passes it middle+1 and last.
    Expand
  4. Re-write the code so that it no longer has this inefficiency.


        public static int search(int first,  int last, 
                                 int target, int[] data)
        {
            int      middle, result;
            
    
            result = -1;
            
            if (first == last) // The base case
            {
                if (target == data[first])
                {
                    result = first;                
                }
            }
            else              // Refinement (i.e., shrink the search space)
            {
                if ((target >= data[first]) && (target <= data[last]))
                {
                    middle = (first + last) / 2;
                    result = search(first, middle, target, data);
                    if (result < 0)
                    {
                        result = search(middle + 1, last, target, data);
                    }
                }
            }
            return result;
        }
    
    Expand
5. Debugging Output in Recursive Methods: This part of the lab will help you understand how to trace recursive algorithms using "debugging" output.
  1. Open the ChangeMakerDriver class.
  2. Change the size of the coins array in ChangeMakerDriver to 3.
  3. Comment-out the statement that assigns 10 to coins[3] in ChangeMakerDriver.
  4. Execute the ChangeMakerDriver passing it the command-line parameter 4.
  5. What output was generated?


        Coins needed for 4 cents: 4
        
    Expand
  6. Print cards for the change() method. (Note: If you're working on this during a scheduled lab period, pre-printed cards are available.)
  7. Trace the execution of the change() method in the ChangeMaker class when it is passed the array {1, 5, 8} and the value 4.
  8. What answer did you get? (Note: You know what answer you should get! If you didn't get the correct answer, try one more time and then leave it for later.)


    4
    Expand
  9. Open the ChangeMaker class.
  10. Modify the ChangeMaker class so that it keeps track of the number of times the change() method is called.
  11. What changes did you make?


    I added an int attribute named methodCalls, added a constructor that initialized it to 0, and increased it by 1 at the top of the change() method. (In preparation for the final exam: You should understand why methodCalls needs to be an attribute and not a local variable. You should also understand why this attribute should not be static. You should think about the implications of making both the change() method and the methodCalls attribute static.)

    I also added a resetMethodCallCounter() method that I could call to assign the value 0 to methodCalls and an accessor method.

    Expand
  12. Modify the ChangeMaker class so that it prints the following "debugging" output:
    • amount when the method is entered
    • number each time it changes
    • min each time it changes
  13. What changes did you make?


        public int change(int[] coins, int amount) 
        {
            int i, j, min, number;
    
            methodCalls++;
            
            System.out.println(" ");
            System.out.println("=== Entering change() for the " + 
                               methodCalls+" time ===");
            min = amount;
            
            System.out.println("amount: "+amount);
    
            // If we can make change with one coin then we are done
            //
            for (i=0; i < coins.length; i++) {
                
                if (coins[i] == amount) return 1; // The base case
            }
            
            // Refine the solution
            //
            for (j=1; j <= (amount/2); j++) {
                
                number = change(coins, j) + change(coins, amount-j);
                System.out.println("j: "+j+"\tnumber: "+number);
                if (number < min)
                {
                    System.out.println("\tmin: "+number);
                    min = number;
                }
            }
            
            return min;
        }    
        
    Expand
  14. Change the size of the coins array in ChangeMakerDriver to 4.
  15. Assign 10 to coins[3] in ChangeMakerDriver.
  16. Execute the ChangeMakerDriver passing it the command-line parameter 4.
  17. What was output?


    === Entering change() for the 1 time ===
    amount: 4
    
    === Entering change() for the 2 time ===
    amount: 1
    
    === Entering change() for the 3 time ===
    amount: 3
    
    === Entering change() for the 4 time ===
    amount: 1
    
    === Entering change() for the 5 time ===
    amount: 2
    
    === Entering change() for the 6 time ===
    amount: 1
    
    === Entering change() for the 7 time ===
    amount: 1
    j: 1    number: 2
    j: 1    number: 3
    j: 1    number: 4
    
    === Entering change() for the 8 time ===
    amount: 2
    
    === Entering change() for the 9 time ===
    amount: 1
    
    === Entering change() for the 10 time ===
    amount: 1
    j: 1    number: 2
    
    === Entering change() for the 11 time ===
    amount: 2
    
    === Entering change() for the 12 time ===
    amount: 1
    
    === Entering change() for the 13 time ===
    amount: 1
    j: 1    number: 2
    j: 2    number: 4
    

    Expand
  18. One of the things that makes the "debugging" output difficult to interpret is that the change() method is called from two different places in the change() method. Add "debugging" output statements that indicate which call to change() is being traced.
  19. What changes did you make?


                System.out.println("   ---Recursing from the left");
                numberFromLeftCall = change(coins, j);
    
                System.out.println("   ---Recursing from the right");
                numberFromRightCall = change(coins, amount-j);
    
                number = numberFromLeftCall + numberFromRightCall;
        
    Expand
  20. Comment-out your "debugging" output statements.
  21. Execute the ChangeMakerDriver using the array {1, 5, 8, 10} and the value 13.
  22. What was output? How many method calls were made?


        Coins needed: 2
        

    The change() method was called 889 times.

    Expand
6. Looking Ahead: This part of the lab will give you some experience with one of the topics that you will study in CS240. (Note: There are no "hints" for this section.)
  1. Execute the ChangeMakerDriver using the array {1, 5, 8, 10} and the value 32.
  2. About how long did it run in "clock time"?


  3. Execute the ChangeMakerDriver using the array {1, 5, 8, 10} and the value 36.
  4. About how long did it run in "clock time"?


  5. What can you conclude about this algorithm? (Note: You will formalize this notion in CS240 when you study the analysis of algorithms.)


Copyright 2021