Answers to Exam 2 Practice Problems
CSSE 221 – Fundamentals of Software Development Honors
Fall 2008–2009

Exam topics and Practice problems, with Answers

    Big-Oh

  1. [5 points] The definition of big-Oh. Be able to repeat the definition, substituting particulars for f and g as required. Sample problem:

    1. According to the definition of big-Oh, what does it mean when you say The space required by MergeSort to sort an array of length p is O(p)?

      Answer: It means that there exists a k and an m such that the space required by MergeSort to sort an array of length p, for p > m, is ≤ kp

  2. [5 points] Determining the big-Oh class for a given function, by using shortcut rules. Sample problems:

    1. Fill in the blank in the most appropriate way: 7n1.5 + 25n2 log n + 1.1n + 703 is O(1.1n).
    2. Fill in the blank in the most appropriate way: 7n1.5 + 25n2 + 37n log n + 703 is O(n2).
  3. Data structures for implementing Collections interfaces

  4. [10 points] Implementing Collections interfaces: strengths and weaknesses of various data structures. Sample problems:

    1. We discussed four primary data structures for implementing Java Collections Framework interfaces: resizable array, linked list, tree, and hash table. For each of these, state its main advantage(s) and its main disadvantage(s).\

      Answer:

      Data structure Main advantage(s) Main disadvantages
      Resizable array Index to any position in O(1) time Inserting requires O(n) time to shift elements; growing array requires O(n) time to copy elements
      Linked list Insert/remove in O(1) time if iterator points to place to insert/remove O(n) time to access nth element if starting from front of list
      Tree O(log n) find/insert/remove if a balanced search tree; excellent guaranteed performance Lookup not quite as fast as a good hash table
      Hash table O(1) time (great!) to lookup data associated with a key, if the hash function is good; generally excellent insert/lookup performance Lookup time degrades badly, to as bad as O(n), if the hash table has lots of collisions; good insert/lookup performance is not guaranteed
    2. Explain how to use a hash table to implement the Map interface.

      Answer: Apply the hash function to the key (using O(1) time) to get an index into the hash table. That index points to a linked list (or other data structure); search that (hopefully short) linked list sequentially to find the key/data pair for the searched-for key.

    3. Explain how to use a binary search tree to implement the Map interface.

      Answer: Do a binary search on the binary search tree to find the key (taking at most O(log n) time), and its associated data.

    4. Suppose you want to implement the Map interface. Two of its implementations are TreeMap and HashMap. Under what circumstances is TreeMap the better choice? Why? Under what circumstances is HashMap the better choice? Why?

      Answer: TreeMap provides O(log n) guaranteed find/insert/remove; use TreeMap if you need a guarantee of no worse than O(log n). HashMap generally provides O(1) find/insert/remove, but can degrade to poor performance if the hash function has many collisions; use HashMap if you want excellent performance most of the time and you believe that you can control the hash function and data to avoid frequent collisions.

    5. Which of the above four data structures (resizable array, linked list, tree, and hash table) would be the best choice for implementing the SortedSet interface? Why?

      Answer: Tree is best because it can keep the data in a sorted form that allows fast lookup/insert/remove.

    6. What is the main advantage of a contiguous implementation of a List over a non-contiguous implementation?

      Answer: Indexing into the List takes O(1) time in a contiguous implementation but O(n) time in a non-contiguous implementation.

      What is the main advantage of a non-contiguous implementation of a List over a contiguous implementation?

      Answer: If you are iterating through the non-contiguous implementation, you can insert or remove an element at the iteration point in O(1) time, while doing so in the contiguous implementation takes (in general) O(n) time.

  5. [10 points] Resizable arrays: Determining the big-Oh class for the run-time of a given operation on a given data structure. Sample problems [Be able to explain your answers to any of these sample problems]:

    If resizable array X has m elements:

    1. What is the run-time of adding an element:
      • to the beginning of X? Answer: O(m)
      • to the middle of X? Answer: O(m)
      • to the end of X? (State any assumptions you need here.) Answer: O(1) if the array does not need to grow, but O(m) if the array does need to grow
      • to the 5th spot in X? Answer: O(m)
      • to the 5th spot from the end of X? (State any assumptions you need here.) Answer: O(1) if the array does not need to grow, but O(m) if the array does need to grow
    2. What is the worst-case run-time of finding in X an element whose key is K:
      • if X is sorted? Answer: O(log m) [by using binary search]
      • if X is NOT sorted? Answer: O(m) [by using sequential search]
    3. What is the best-case run-time of finding in X an element whose key is K:
      • if X is sorted? Answer: O(1)
      • if X is NOT sorted? Answer: O(1)
    4. What is the run-time of traversing (i.e., iterating through) X? Answer: O(m)
  6. [10 points] Linked lists: Determining the big-Oh class for the run-time of a given operation on a given data structure. Sample problems [Be able to explain your answers to any of these sample problems]:

    If linked list X has m elements:

    1. What is the run-time of adding an element:
      • to the beginning of X? Answer: O(1)
      • to the middle of X? Answer: O(m)
      • to the end of X? (State any assumptions you need here.) Answer: O(m), but O(1) if the linked list has a pointer to its last element (as a doubly-linked list would)
      • to the 5th spot in X? Answer: O(1)
      • to the 5th spot from the end of X? (State any assumptions you need here.) Answer: O(m), but O(1) if the linked list has a pointer to its ast element (as a doubly-linked list would)
      • to the spot to which your iterator is pointing? Answer: O(1)
    2. What is the worst-case run-time of finding in X an element whose key is K:
      • if X is sorted? Answer: O(m) [binary saerch does not help because you cannot index into the middle in O(1) time]
      • if X is NOT sorted? Answer: O(m)
    3. What is the best-case run-time of finding in X an element whose key is K:
      • if X is sorted? Answer: O(1)
      • if X is NOT sorted? Answer: O(1)
    4. What is the run-time of traversing (i.e., iterating through) X? Answer: O(m)
  7. [10 points] Trees: Determining the big-Oh class for the run-time of a given operation on a given data structure. Sample problems [Be able to explain your answers to any of these sample problems]:

    If binary tree X has m elements:

    1. What is the run-time of adding an element:
      • to the root of X? Answer: O(1)
      • to a leaf of X, if X is balanced? Answer: O(log m)
      • to a leaf of X, if X is completely UN-balanaced? Answer: O(m)
    2. What is the worst-case run-time of finding in X an element whose key is K:
      • if X is a balanced, binary search tree (i.e., is sorted)? Answer: O(log m)
      • if X is NOT a search tree (i.e., is NOT sorted)? Answer: O(m)
    3. What is the best-case run-time of finding in X an element whose key is K:
      • if X is a balanced, binary search tree (i.e., is sorted)? Answer: O(1)
      • if X is NOT a search tree (i.e., is NOT sorted)? Answer: O(1)
    4. What is the run-time of traversing (i.e., iterating through) X? Answer: O(m)
    5. What is the run-time of traversing (i.e., iterating through) a balanced binary tree if its DEPTH is n? Answer: O(2n)
  8. [10 points] Hash tables: Determining the big-Oh class for the run-time of a given operation on a given data structure. Sample problems [Be able to explain your answers to any of these sample problems]:

    If hash table X has m elements:

    1. What is the run-time of (state any assumptions that you need for each of your answers):
      • finding in X the data associated with a key K? Answer: O(1) if the hash function does not yield many collisions, but as bad as O(m) if there are many collisions
      • inserting new data for key K? Answer: Same as finding
      • adding a new key/data pair? Answer: Same as finding
  9. Recursion

  10. [5 points] Recursion: definition. Sample problem:

    1. What is the definition of a recursive method? Answer: A method that calls itself.
  11. [10 points] Recursion: advantages and disadvantages. Sample problems:

    1. What are the main advantages of implementing an algorithm recursively? The main disadvantages? Answer: Advantages: easy to implement and prove correct if implementing from a recursive definition, description or data structure. Disadvantages: may take more space and time than an iterative implementation.
    2. List two techniques applicable to recursive methods that address the disadvantages of recursive implementations. Answer: Memory tables and tail recursion.
    3. Explain what a memory table (aka memory function) is, how it is used, and why it is used. Answer: When you compute a value recursivey, store it; before you compute a value, see if you have already stored it (and if so, return the stored value). Use a memory table to prevent recomputing values in a recursive calculation.
    4. Explain what tail recursion is and why it is used. Also give an example of a tail recursive method. Answer: Tail recursion is recursion in which no additional computation occurs after the return from the recursive call. It is used because good compilers can convert tail-recursive functions to iterative (hence space and time efficient) solutions; you get the best of both worlds: clarity from the recursive implementation and efficiency from the compiler-generated iterative solution.
  12. [10 points] Writing recursive methods for recursive definitions. Sample problems:

    1. The nth Padovan number P(n) is given by the following definition:
          P(0) = P(1) = P(2) = 1
          P(n) = P(n-2) + P(n-3) for n > 2
      
      Write a recursive method that returns the nth Padovan number. Answer:
          int P(int n) {
              if (n <= 2) {
                  return 1;
              } else {
                  return P(n-2) + P(n-3);
              }
           }
      
    2. The Ackermann function A is given by the following definition:
          A(0, n) = n + 1
          A(m, 0) = A(m-1, 1) if m > 0
          A(m, n) = A(m-1, A(m, n-1)) if m > 0 and n > 0
      
      Write a recursive method that computes Ackermann(m, n) for any positive integers m and n. Answer:
          int A(int m, int n) {
              if (m == 0) {
                  return n + 1;
              } else if (n == 0) {
                  return A(m-1, 1);
              } else {
                  return A(m-1, A(m, n-1))
              }
          {
      
      Aside: you might enjoy figuring out how quickly Ackermann's function grows: just A(4, 2) requires 19,729 decimal digits!!!
  13. [10 points] Writing recursive methods for recursive data structures. Sample problems:

    1. Sample problem: What is a recursive data structure? Give an example of a recursive data structure. Answer: A recursive data structure is one which has a field that is the same type (class) as the data structure itself. For example, the Node structure of a linked list:
           class Node {
               E element;
               Node next;
           } 
      
    2. Sample problem: A linked list must contain a recursive structure. Suppose that recursive structure is called Node. What are the fields (types and meaningful names) of that recursive structure? Answer: See previous problem.
    3. Sample problem: Consider a linked list whose recursive structure is as follows:
          class Node {
              E element;
              Node next;
          }
      
      Write a recursive method add(E e) in the Node class that adds the given element to the end of the linked list. Note that this method is a member of the Node class; you do not need to deal with an empty linked list in this problem. Answer:
          private void add(E e) {
              if (this.next == null) {
                  this.next = new Node(element);
              } else {
                  this.next.add(e);
              }
           }
      
    4. Sample problem: Consider a linked list whose recursive structure is as above. Write a recursive method get(int n) in the Node class that returns the element in the nth node from the current node in the linked list. Note that this method is a member of the Node class; also, you may assume that n is not greater than the length of the remaining list. Answer:
          private E get(int n) {
              if (n == 0) {
                  return this.element;
              } else {
                  return this.next.get(n - 1);
              }
           }
      
    5. The following defines a ternary tree (i.e., each non-leaf has 3 children):
          class TernaryTreeNode {
              E data;
              TernaryTreeNode left;
              TernaryTreeNode middle;
              TernaryTreeNode right;
          }
      
      Write a recursive method in the TernaryTreeNode class that prints the data on the rightmost branch (root to leaf) of the tree, with the root first and the leaf last. Answer:
          private void print() {
              System.out.println(this.data);
              if (this.right != null) {
                  this.right.print();
              }
           }
      
    6. Continuing the previous problem, write a recursive method in the TernaryTreeNode class that prints the data on the rightmost branch (root to leaf) of the tree (as in the previous problem), but now with the leaf first and the root last. Answer:
          private void print() {
              if (this.right != null) {
                  this.right.print();
              }
              System.out.println(this.data);
           }
      
    7. Continuing the previous problems, write a recursive method in the TernaryTreeNode class that returns the number of nodes in the tree whose value for E equals null. Answer:
          private int countNulls() {
              int count = 0;
              if (this.data == null) {
                  count = 1;
              }
              if (this.left != null) {
                  count += this.left.countNulls();
              }
              if (this.middle != null) {
                  count += this.middle.countNulls();
              }
              if (this.right != null) {
                  count += this.right.countNulls();
              }
              return count;
           }
      
  14. [10 points] Writing recursive methods for recursive algorithms. Sample problems:

    1. Write a recursive method that, given a sorted array X of integers and an integer K, returns the index of the array where K appears (or -1 if K does not appear in the array). (You may use a helper method if you wish. There is more than one correct answer to this problem.) Answer: Here is binary search (you could do sequential search recursively also):
          private int find(int k, int[] x) {
              return find(int k, int[x], 0, x.length - 1);
          }
      
          private int find(int k, int[] x, int begin, int end) {
              if (begin > end)
                  return -1;
              } else {
                  int middle = (begin + end) / 2;
                  if (k < x[middle]) {
                      return find(k, x, begin, middle - 1);
                  } else if (k > x[middle]) {
                      return find(k, x, middle + 1, end);
                  } else {
                      return middle;
                  }
              }
          }
      
    2. Write the (recursive) algorithm for MergeSort, GIVEN the merge method. Write in English, not in Java, and do NOT write merge; just write MergeSort. Answer:
          if the array has 1 element, return.
          MergeSort the left half of the array.
          MergeSort the right half of the array.
          Merge the two halves.
      
  15. Searching and Sorting

  16. [10 points] Searching. Sample problems:

    1. State the algorithm for sequential search for key K in an array X of integers. Answer:
      Iterate through the data in some order.
      • If you find K, return the current index.
      • If you get through all the data without finding K, return -1.
    2. State the algorithm for binary search for key K in a sorted array X of integers. Answer: When searching the array from begin to end (initially from 0 to X.length - 1):
      • If begin > end, return -1 (key K is not in the array).
      • Let middle = (begin + end) / 2.
        • If K equals X[middle], return middle.
        • If K < X[middle], recurse on the array from start to middle - 1.
        • If K > X[middle], recurse on the array from middle + 1 to end.
    3. State the algorithm and relevant data structures for binary search for key K in a binary search tree X whose keys are integers. Answer: When searching the binary search tree at node X:
      • If K equals X's key, return X's data.
      • Otherwise, if K < X's key:
        • If X's left child is null, return -1 (key K is not in the binary search tree).
        • Else return binarySearch recursively applied to X's left child.
      • Otherwise (since K > X's key):
        • If X's right child is null, return -1 (key K is not in the binary search tree).
        • Else return binarySearch recursively applied to X's right child.
    4. When an array is sorted, which is a better choice: binary search or sequential search? Answer: Binary search -- it is much faster.
    5. When an array is NOT sorted, which is a better choice: binary search or sequential search? Answer: Sequential search -- binary search does not work correctly on unsorted data, and, for a single search, it is not worth the O(n log n) time to sort.
    6. What is the worst-case run-time for finding a key K using sequential search on an array X of length n? Best-case run time? Average-case run-time? [Be able to explain your answers.] Answer: Worst-case: O(n). Best-case: O(1). Average-case: O(n).
    7. What is the worst-case run-time for finding a key K using binary search on a sorted array X of length n? Best-case run time? Average-case run-time? Answer: Worst-case: O(log n). Best-case: O(1). Average-case: O(log n). [Be able to explain your answers.]
    8. What is the worst-case run-time for finding a key K using binary search on a balanced binary search tree with n nodes? Best-case run time? Average-case run-time? [Be able to explain your answers.] Answer: Worst-case: O(log n). Best-case: O(1). Average-case: O(log n).
    9. What is the worst-case run-time for adding a key K and its associated data in a balanced binary search tree with n nodes (where you need NOT keep the tree balanced)? Best-case run time? Average-case run-time? [Be able to explain your answers.] Answer: Worst-case: O(log n). Best-case: O(1). Average-case: O(log n).
    10. What is the advantage of doing binary search on a binary search tree instead of on a sorted array? What is the disadvantage? Answer: Advantage: add/remove can be done in O(log n) in a binary search tree, as opposed to O(n) in general in a sorted array. Disadvantage: Requires extra space for the tree pointers. (But still O(n) space for both binary search tree and sorted array.)
  17. [10 points] Sorting. Sample problems:

    1. When we talk about the "best-case run-time" for a sorting algorithm, what do we mean? What do we mean by the "average-case run-time"? Answer:

      Best-case run-time means the best run-time of all instances of size N, where N is the size of the problem under consideration.

      Average-case run-time means the run-time, averaged over all instances of size N, where N is the size of the problem under consideration. Note that this requires a probability distribution on instances of size N -- unless specified otherwise, we usually assume that all instances are equally likely (even though this assumption is unlikely to be valid in practice).

    2. Here is a description of how SelectionSort operates on an array X on length N:
          1. Traverse X to find the smallest element in X.  Put that smallest element into X[0].
          2. Repeat step 1 to find the 2nd smallest (putting it into X[1]).
          3. Repeat step 1 to find the 3rd smallest (putting it into X[2]).
          4. Etc until all N elements are done.
      
      Be able to explain your answers to the following:
      • What is the worst-case run time of SelectionSort? Answer: O(n2).
      • What is the best-case run time of SelectionSort? Answer: O(n2).
      • What is the average-case run time of SelectionSort? Answer: O(n2).
      • How much extra space (beyond the space for X) does SelectionSort require? Answer: O(1).
    3. Here is a description of how MergeSort operates on an array X from indices start to end:
          1. If start >= end, stop (the array is sorted in the range from start to end).  Otherwise:
          2. Recursively MergeSort the left half of X (from start to the middle).
             Recursively MergeSort the right half of X (from middle to end).
          3. Merge the two sorted halves [as described in class].
       
      Be able to explain your answers to the following:
      • What is the worst-case run time of MergeSort? Answer: O(n log n).
      • What is the best-case run time of MergeSort? Answer: O(n log n).
      • What is the average-case run time MergeSort? Answer: O(n log n).
      • How much extra space (beyond the space for X) does MergeSort require? Answer: O(n) (although there are sophisticated versions of MergeSort that require only O(1) extra space).
    4. Here is a description of how a space-inefficient version of QuickSort operates on an array X from indices start to end:
          1. If start >= end, stop (the array is sorted in the range from start to end).  Otherwise:
          2. Let Pivot = X[start].
             For k from start + 1 to end:
               -- If X[k] <= Pivot, copy X[k] into the next spot of temporary array called LessThanPivot.
               -- If X[k] >  Pivot, copy X[k] into the next spot of temporary array called MoreThanPivot.
          3. Recursively QuickSort LessThanPivot.
             Recursively QuickSort MoreThanPivot.
          4. Copy the sorted LessThanPivot into X,
             then put Pivot into the next position in X,
             then copy the sorted MoreThanPivot into the remaining positions in X.
        
      Be able to explain your answers to the following:
      • Explain why QuickSort does in fact sort array X. Answer: After step 2 completes, pivot is in the correct place, all keys less than pivot are to the left of pivot, and all keys greater than pivot are to the right of pivot. So, step 3 completes the sort by sorting the two halves.
      • What is the worst-case run time of QuickSort? Answer: O(n2) - consider a perfectly sorted array, for example.
      • What is the best-case run time of QuickSort? Answer: O(n log n) - if the LessThanPivot and MoreThanPivot arrays are of approximately equal size.
      • What is your best guess for the average-case run time QuickSort? (no analysis required) Answer: O(n log n).
      • How much extra space (beyond the space for X) does this version of QuickSort require? Answer: O(n) in the above description, but only O(1) in more reasonable implementations.
    5. [5 points] Function objects: what are they, how are they used, why are they useful, Comparable and Comparator as examples.

      1. Sample problem: What is a function object? Answer: an object that exists only to carry a function (method) with it.
      2. Sample problem: Is a Comparator a function object? Explain. Answer: Yes, it carries along its compare method.

    Total: 130 points (but the exam will cover only 100 of the above 130 points, i.e., some topics will not show up on the exam)