Computer Science 15-111 (Sections A & B), Spring 2007

Homework #3 (part 1)

  Due:  Fri 26-Jan-2007 at 10:30am (online submission).

 

Place the written portions of the following problems in the file “hw3-written.txt” in your online submission of hw3.zip.  Remember:  Show your work!

 

1.   In a few words of plain English, what does this method compute?  Explain.
   public static boolean foo(int[] x) {

           for (int i=1; i<x.length; i++)

                if ((x[i] <= 0) ||

                    (x[i-1] <= 0) ||

                    (x[i] / x[i-1] == 0))

                     return false;

           return true;
     }

2.      Write a program, Median.java, that reads in a list of integers using scanner.nextInt(), ending when the sentinel -1 is entered, and prints out the median of the data (for a definition of median, see Wikipedia’s entry on “median”).  Note that even for integral data, the median may be a float – for example, the median of { 2, 3 } is 2.5.  Note that we discussed a static method in the Arrays class that is especially helpful here.

3.      Write a program, RemoveDuplicates.java, that reads in a list of integers ending with the sentinel -1, and then prints out the same numbers in sorted order, only with duplicates removed.  For example, with user input underlined:
     Enter data:   3   1   1   32    3    1    44    32    -1
     Sorted data without duplicates:  1   3   32   44
Note that here, as in general, the sentinel is not considered part of the data.

4.      A “trace” of a program’s execution shows each method call, and each value returned, nesting and indenting these calls to indicate the call stack (thus, for example, any recursion that may occur).  For example, consider the following method that computes the nth Fibonacci number:

   public static int fib(int n) {
        if (n < 2) return 1;
        return fib(n-1) + fib(n-2);
   }


Here is a trace of a call to fib(3), where arrows indicate return values:
fib(3)
   fib(2)
      fib(1)
      --> 1
      fib(0)
      --> 1
   --> 2
   fib(1)
   --> 1
--> 3

This trace clearly shows that fib is recursive, and for example that fib(3) calls fib(2), which in turn calls fib(1) and fib(0), after which fib(3) calls fib(1).  This also shows that fib(1) is called twice.  Once by fib(2) and again by fib(3).

If it helps, for instructional or debugging purposes, you can instrument a method to produce its own trace (though you must also be able to write traces by hand!).  For example, here is an instrumented version of fib that produces precisely the trace above:
   public static int fib(int n) {
        return fib(n,0);
   }

   public static int fib(int n, int depth) {
        // output method call, indented by recursive depth
        for (int i=0; i<3*depth; i++) System.out.print(" ");
        System.out.printf("fib(%d)\n",n);

        // compute the result, recursing using the version
        // that includes the depth in the signature
        int result;
        if (n < 2) result = 1;
        else result = fib(n-1,depth+1) + fib(n-2,depth+1);

        // output result, indented by recursive depth
        for (int i=0; i<3*depth; i++) System.out.print(" ");
        System.out.printf("--> %d\n",result);

        // and return the result
        return result;
   }

Notice that this uses method overloading so that fib(int n) can still be called, although it is now just a wrapper for fib(int n, int depth) so that we can keep track of the depth of recursion, which we use to properly indent the output.

a.      Trace a call to fib(4).

b.      Write a program, TracedFib1.java, which reads in a positive integer M and prints out a table showing, for each value N from 0 to M, the number of calls made to fib(1) while computing fib(N).  For example, your program may run as follows (where user input is underlined):
   Enter an upper bound for fib(1) tracing:  3
   N      calls to fib(1) while computing fib(N)
   0          0
   1          1
   2          1
   3          2
To write this program, include a static variable fib1calls, and in your main method, repeatedly set this variable to 0, call fib(n), then print out the resulting value of fib1calls.  Now, in fib itself, test if n==1, and if so, increment fib1calls.

c.      Briefly explain why fib1calls must be a static variable.

d.      Extend the table from part (b) up to N=10.

e.      Given this table, how many calls to fib(1) are required, in general, when computing fib(n) recursively?  (Note:  Given that Fibonacci numbers grow exponentially fast, this should help you understand why the recursive version of fib slows down so much around n=40.)

5.      Memoization (a kind of dynamic programming) is a technique which uses caching in order to combine the benefits of the clarity of recursive solutions with the speed of  iterative solutions.  Write a program, MemoizedFib.java (note that there is no “r” in Memoized), that uses memoization to dramatically speed up our recursive fibonacci method.  The basic idea is to start with the recursive solution, and to store the results of calls to fib(i), so that repeated calls just use the stored values rather than recursively recomputing them.  To do this, create an array of int’s, fibValues.  For this problem, you can fix the maximum value of N to 99, and so you can set the size of fibValues to 100.  You should also have a second array, isComputed, containing boolean values such that isComputed[n] is true if and only if fibValues[n] holds the value of fib(n) (hence, initially isComputed[n] should be false for all values of n, which conveniently is the default when you create an array of booleans).  Now, on a call to fib(n), first check if isComputed[n] is true.  If so, just return fibValues[n].  Otherwise, compute fib(n) recursively (still using memoization in your recursive calls!), and then set fibValues[n] to the result and isComputed[n] to true before returning the result.  Write some test code that verifies that your memoized fib computes the same values as your recursive fib.  Notice, though, that the runtimes of memoized fib are basically the same as iterative fib.

6.      Consider the following method:
     
public static int foo(int x, int y) {

           if ((x < 1) || (y < 1)) return 0;

           else return foo(x-1,y-1)+x+y-1;

     }

a.   Trace a call to foo(8,3)

b.   Trace a call to foo(8,4)

c.   In just a few words of English, assuming its arguments are both non-negative, what does this method compute?  (Hint:  it is a common arithmetic expression).

d.   Explain why your answer to part (c) is correct.