Problems from N.R. Wagner for the CS3343 Final Exam, Spring 2004

1. (a) Use induction to prove the following matrix identity:
   
               n
   |  0   1  |         |  F(n-1)   F(n)   |
   |         |     =   |                  |, where n >= 1
   |  1   1  |         |   F(n)   F(n+1)  |

   Here F(n) is the nth Fibonacci Number:
   F(0) = 0, F(1) = 1, and F(n) = F(n-1) + F(n-2), for n >= 2.
   (So F(2) = 1, F(3) = 2, F(4) = 3, F(5) = 5, F(6) = 8, ....)
   
   The above formula involves matrices, and on the left,
   we are multiplying the matrix times itself n times.
   
   For full credit you MUST clearly indicate what you are
   assuming and what you are proving.  It is not enough just
   to write some formulas.
   
   (b) In class, we discussed two efficient algorithms for raising
   a number to a power.  If n is the exponent, what is the
   Big-Theta performance of either of these raise-to-a-power
   algorithms in terms of n.
   
   (c) In class, we discussed how the formula in part (a) gave a
   more efficient way to calculate F(n), the nth Fibonacci Number.
   Say briefly how you can efficiently use the formula to
   calculate F(n).  Give the Big-Theta performance of this
   algorithm.
   
2. Consider the following algorithm that we studied in class:

      A = array with n elements, indexed by 0 to n-1 inclusive;

      void randomize(type[] A) {
         for (int i = 0; i < n-1; i++) {
            int r = randomInt(i, n-1); // i to n-1 inclusive
            interchange(A[i], A[r]);   // possibly i == r
         }
      }
      
      (Here randomInt(a, b) returns a true random int r,
      satisfying a <= r <= b such that each of the integers
      between a and b (inclusive) is equally likely to occur.)
      
      (a) Trace through the execution of this function, showing
      the result after each loop iteration, using as input the
      array of integers A = {6, 7, 8, 9}, so that n == 4,
      and "type" is "int".  Assume the function randomInt 
      returns 2, 1, and 3 the three times it is called.
      
      (b) Say in detail what happens if the loop is written
         
           for (int i = 0; i < n; i++) 
         
      with "n" instead of "n-1".   (You must say much more
      than just that "there is another loop iteration.")
      
      (c) What does this function do, and what did we use it
      for in the course?
      
3. The Bell Numbers B(n,k) satisfy the recursive definition:

      B(1, 1) = 1
      B(n, n) = B(n-1, 1), for any n > 1
      B(n, k) = B(n-1, k) + B(n, k+1), for any k >= 1 and n > k
      
   (a) At first glance it might seem as if this formula will not
   work, because the third part of the formula involves n and k+1
   on the right to calculate n and k on the left.  Trace through
   the definition by hand to see how the value for B(3, 1) is
   obtained. (Be careful!)
   
   (b) Write a simple recursive function in C, C++, or Java
   to calculate any Bell number.
   
   (c) Describe in general how the MEMOIZATION technique would
   be used to rewrite the function in part (b) (without actually
   rewriting it).  Why would one want to rewrite it in this way?
   What general algorithm design technique does this represent
   (using a different word or phrase than "memoization")?
   
======================================================================
ANSWERS:
1. (a) For a basis, we assert that the formula is true for n == 1:
      This is obvious, since on the left and the right we have
      the same matrix: 
         |  0   1  | 
         |         |
         |  1   1  |

   Now ASSUME that the formula is true for any n >= 1, that is
   assume that
               n
   |  0   1  |         |  F(n-1)   F(n)   |
   |         |     =   |                  |, where n >= 1
   |  1   1  |         |  F(n)   F(n+1)   |

   We need TO PROVE the corresponding formula for n+1:
               n+1
   |  0   1  |          | F((n+1)-1) F(n+1)    |   | F(n)   F(n+1)  |
   |         |      =   |                      | = |                |
   |  1   1  |          | F(n+1)     F((n+1)+1)|   | F(n+1) F(n+2)  |
   
   But
               n+1                   n
   |  0   1  |           |  0   1  |       |  0   1  | 
   |         |       =   |         |    *  |         |  = (by induction
   |  1   1  |           |  1   1  |       |  1   1  |     assumption)
   
   |  F(n-1)   F(n)   |   |  0   1  |
   |                  | * |         | =  (multiplying out)
   |   F(n)   F(n+1)  |   |  1   1  | 
  
   |  F(n-1)*0 + F(n)*1   F(n-1)*1 + F(n)*1  | 
   |                                         |  =  (simplify)
   |   F(n)*0 + F(n+1)*1  F(n)*1 + F(n+1)*1  |  
  
   |   F(n)     F(n-1) + F(n)  | 
   |                           |  =  (properties of Fibonacci numbers)
   |   F(n+1)   F(n) + F(n+1)  |  
  
   |   F(n)     F(n+1)  | 
   |                    |, which is what was to be proved.
   |   F(n+1)   F(n+2)  |  

  This completes the proof by induction.
  [Note: Just giving the final calculation is NOT SUFFICIENT.]
  
  (b) Both of the raise-to-a-power algorithms run in time
  Big-Theta(log n).  This is because their loops iterate through
  the bits of the binary expansion of n, and there are
  log-base2(n) of these bits.
  
  (c) Using the formula in (a) and the efficient raise-to-a-power
  algorithm to multiply the matrices, it is obvious that this is
  a Big-Theta(log n) algorithm to calculate F(n).
  
2. (a) Here are the results of the three loop iterations:
   Initially A = {6, 7, 8, 9}
   i = 0:
      randomInt(0,3) == 2 (by assumption)
      interchange(A[0], A[2])
      A == {8, 7, 6, 9}
   i = 1:
      randomInt(1,3) == 1 (by assumption)
      interchange(A[1], A[1])
      A == {8, 7, 6, 9}  (no change)
   i = 2:
      randomInt(2,3) == 3 (by assumption)
      interchange(A[2], A[3])
      A == {8, 7, 9, 6}
   Final result: A == {8, 7, 9, 6}
   
   (b) There is a additional loop iteration with i == n-1
   In this case, there is a call
      r = randomInt(n-1, n-1), which must return r == n-1
      Then the code interchange(A[n-1], A[n-1]) will have no effect.
   Thus this extra loop iteration has no effect on the result of
   the algorithm, but only makes it run slightly slower.
   
   (c) This function randomizes the order of elements in the array A.
   We used it in class for one version of a randomized Quicksort
   algorithm.  Using randomized input makes the worst-case
   bad performance of quicksort extremely unlikely.
   
3. (a) B(3, 1) = B(2, 1)           +  B(3, 2)
               = B(1, 1) + B(2, 2) +  B(2, 2) + B(3, 3)
               = 1       + B(1, 1) +  B(1, 1) + B(2, 1)
               = 1       + 1       +  1       + B(1, 1) + B(2, 2)
               = 1       + 1       +  1       + 1       + B(1, 1)
               = 1       + 1       +  1       + 1       + 1 = 5
               
   (b)
      int B(int n, int k) {
         if (n == 1 && k == 1) return 1;
         if (n == k) return B(n-1, 1);
         return B(n-1, k) + B(n, k+1);
      }

   (c)
    Using the memoization technique, you create an array,
    call it b[][], that will hold intermediate results of calculating
    Bell numbers.  Initialize b to all -1 values (say).
    During the course of calculating the value B(n,k),
    you first look up in b[n][k] to see if the array entry
    is not -1. If so, just return this value as the answer.
    Then as you call B(n-1,k) and get an answer, store
    this answer into b[n-1][k], and similarly for B(n, k+1).
    
    This illustrates a simple form of DYNAMIC PROGRMMING.