Fundamentals of Computer Science II (CSC-152 98S)


Sample Exam 1, With Solutions

This is the first exam I gave to the Fall 1997 Session of CS152. Students took this exam near the end of seventh week (about 1.5 weeks later than you will) and had about two hours to complete the exam. Unfortunately, many of the problems deal with lists, which we haven't covered. However, your experience with lists in CS151 should make the general questions clear enough.

Question One: Object Design and Code Reuse [20 points]

Summarize the modifications you would have to make to your Othello game to implement the following rule changes:

Note that you do not need to make the modifications, simply summarize all the changes to your program you would need to make to get these extensions to work.

Grading: Your answer depended on your Othello implementation. In general, I looked for intelligent solutions that talked about use of constants for board size, a new (or improved) Piece class, and so on and so forth.

Question Two: Lists, Preconditions, Exceptions, and More [40 points]

One of the reasons we have a current field in our List classes is that it allow us to step through the list, making modifications as we go. Assume that you're working with a doubly-linked list and write the following functions that pertain to current, making sure that you

The question is intended to test your understanding of lists, preconditions, postconditions, and exceptions. The logic you employ is much more important than precise Java or javadoc syntax.

A: Resetting the current element [10 points]

Implement void resetCurrent() which makes first element of the list the current element of the list (that is, that resets the current "pointer" to the front of the list).

Grading: In looking at your solutions, I checked whether you indicated that the function could throw an exception and whether you actually through one. I also checked to make sure that you had reasonable pre- and post-conditions.

Sample Solution:

/** 
 * Make the current element of the list refer to the front of the list.
 * pre: The list is nonempty.
 * post: The current reference refers to the first element.
 * post: The list is otherwise unchanged.
 */
public void resetCurrent() 
  throws ListException 
{
  // Is the list empty?  If so, this will be a problem.
  if (front == null) {
    throw new ListException("Can't reset current element in empty list.");
  }
  // No exception has been thrown, so we're okay
  current = front;
} // resetCurrent

B: Advancing the current element [10 points]

Write boolean findElement(Object elt) which advances the current reference to the first instance of elt after the current current reference, stopping when it hits the end of the list. The function returns true if the element is found, and false otherwise.

For example, if our list were A,E,B,E,D,F,E, and current were initially at the beginning of the list, the first call to findElement(E) would move current to the second position, the next call would move it to the forth position, the next call would move it to the seventh position, and any subsequent calls would return false.

Grading: In looking at your solutions, I again checked for exceptions and pre- and post-conditions. I also checked to make sure that you used equals for your comparisons and that you met the specifications of the problem. One particular problem was failing to specify what happened to the current reference if there were no further instances of the element. Many of you also had a number of small or large errors. Some failed to find the element when it immediately followed the current element. Some failed to find the element when it came at the end of the list. Some forgot about the difference between objects and nodes.

Sample Solution:

/**
 * Advance the current pointer to the first instance of elt after
 * the current instance.  If no other instances are found, the
 * current pointer is returned to the front of the list.
 * pre: The list is nonempty (not strictly necessary).
 * pre: The parameter is a comparable object.
 * pre: The current pointer currently points to some element in the list.
 * post: The current pointer now points to the next instance of
 *       the element (if such an element exists) or to the front
 *       of the list.
 * post: The elements in the list have not been modified.
 */
public boolean findElement(Object element) throws ListException {
  // Check for errors
  if (current == null) {
    throw new ListException("No current element!");
  }
  if (front == null) {
    throw new ListException("Empty list");
  }
  // Start at the next element
  current = current.next;
  // Keep going until we find the right place or reach the
  // end of the list.  We use a return to exit if we've found
  // the right place.
  while (current != null) {
    if (element.equals(current.value)) {
      return true;
    }
  } # while
  // The element wasn't found, so reset current and return false.
  current = front;
  return false;
} // findElement

C: Inserting after the current element [20 points]

Write void insertAfterCurrent(Object elt) which inserts an element in the list after the current element of the list list. Make sure that you think about the various special cases and account for them in your preconditions, exceptions, and/or code.

Grading/Problems: The biggest problem many of you had was considering all of the special cases. These include the empty list and the list in which the current element is the last element. Some of you also had problems with pointer swapping to correctly insert the element.

Sample Solution:

/**
 * Insert a value after the current element in the list.
 * pre: The list is nonempty.
 * pre: There is a current element
 * post: The size of the list increases by 1
 * post: The elements before the current element are in the same
 *       order.
 * post: The elements after the current element are in the same
 *       order.
 * post: The new element is directly between the current element
 *       and its previous successor.
 */
public void insertAfterCurrent(Object elt) 
  throws ListException 
{
  // Error checking
  if (current == null) {
    throw new ListException("No current element!");
  }
  if (front == null) {
    throw new ListException("Empty list");
  }
  // Special case: at end of list
  if (current == back) {
    appendToEnd(elt);
    return;
  }
  // Normal case, create a node whose previous pointer refers to the
  // current element and whose next pointer refers to the next element.
  // Then update the other pointers to accomodate it.
  Node newelt = new Node(elt,current,current.next);  
  newelt.next.prev = newelt;
  curent.next = newelt;
} // insertAfterCurrent

Question Three: Reading Code and Estimating Running Times [40 points]

Here is a typical implementation of removeAllCopies, which removes all copies of an object from a Vector.

/**
* Remove all copies of elt from the current vector.
* Returns: The number of elements removed.
* Pre: The vector has been initialized.
* Post: No copies of elt are in the current vector.
* Post: All remaining elements are in the same order that they 
*   were before this method was call.
* Post: All original elements not equal to elt are still
*   in the vector.
* Post: The size of the vector is now (old_size - elements_deleted)
*   [this is implied by the previous postconditions]
*/
public int removeAllCopies(Object elt) {
  int i;	// The index of the current element we're considering
  int removed=0;// How many elements have we removed?
  // Step through the vector, removing each copy with the standard
  // removeElementAt method.
  while (i < size()) {
    // Have we found a copy?
    if (elt.equals(elementAt(i))) {
      // Delete that unwanted element
      deleteElementAt(i);
      // Update our count of removed elements
      removed++;
      // Don't update i, since we may now have another copy at that
      // position.
    } // found a copy
    // We haven't found a copy at the current position, so advance
    // to the next position.
    else {
      i++;
    } // haven't found a copy
  } // while
  // Let the caller know how many elements we removed
  return removed;
} // removeAllCopies

A: Running Time [10 points]

What is the worst-case running time of this algorithm? Express your answer in big-O notation.

Answer: The running time of this algortihm is O(n^2). Why? At worst, there can be O(n) deletions, and each deletion takes at most O(n) steps. If we wanted to account for the progressively smaller vectors and deletions, it would be

n-1 + n-2 + ...

which is still O(n^2)

B: Proposed Improvement [10 points]

Joe and Jane Student propose that we can improve our algorithm by deleting elements starting from the right, rather than from the left. Their "improved" version of the algorithm appears as:

public int removeAllCopies(Object elt) {
  int i;	// The index of the current element we're considering
  int removed=0;// How many elements have we removed?
  // Step through the vector, removing each copy with the standard
  // <code>removeElementAt</code> method.
  i = size() - 1;
  while (i >= 0) {
    // Have we found a copy?
    if (elt.equals(elementAt(i))) {
      // Delete that unwanted element
      deleteElementAt(i);
      // Update our count of removed elements
      removed++;
      // Don't update i, since we may now have another copy at that
      // position.
    } // found a copy
    // We haven't found a copy at the current position, so advance
    // to the next position.
    else {
      i--;
    } // haven't found a copy
  } // while
  // Let the caller know how many elements we removed
  return removed;
} // removeAllCopies

i. Does this algorithm work? If not, show a counter example.

Answer: No the algorithm does not work. If the element to be removed is the last element of the vector, then after removing it, there will be an erroneous attempt to reference the now nonexistant last element of the vector.

Repair: If the index is decreased each time, rather than only when we fail to match, the algorithm will work.

ii. What is the worst-case running time of this algorithm (in big-O notation)?

Answer: If the list only contained the element we were deleting, the running time would now be O(n), a significant improvement. However, there are still cases in which it's O(n^2). In particular, consider the situation in which the first n/2 elements all match. We end up shifting n/2 elements n/2 times, which is O(n^2).

iii. In practice, do you think that their algorithm would run faster or slower than the original algorithm?

Answer: It should be faster in all but the single and double match cases, as it does less copying.

C: Another improvement [20 points]

Using at most O(n) more space, write a version of this algorithm that takes O(n) time on all inputs. If you believe that one of the algorithms above takes O(n) time, just say so. Note that n is the number of elements in the vector.

Strategy: Create a new vector, and only copy nonmatching elements into that vector. Then, copy those elements back into the original vector.

Problems: A number of you forgot to ensure that each addition to the new vector was in constant time (which you can ensure by making it big enough to begin with). Some of you forgot to copy the elements back. Some of you forgot to reduce the size after copying the elements back. Some forgot to return the number of elements deleted. There were also the standard problems with equals and such.

Makeup: Some of you got the first or second part wrong, and decided that one of those would be an O(n) algorithm. Those students will be allowed to do a still-to-be-determined makeup.

Sample Solution:

/**
 * Delete all copies of an object from the current vector.  Return
 * the number of objects deleted.
 * pre: Comparable object
 * post: There are no copies of the object left in the vector.
 * post: The remaining objects are in the same order.
 */
public int removeAllCopies(Object elt) 
  throws ListException 
{
  // Create a new vector to hold the nonmatching elements
  Vector save = new Vector(size()); 
  // Step through the old vector, copying nonmatching elements
  for (int i = 0; i < size(); ++i) {
    if (!elt.equals(elementAt(i))) {
      save.addElement(elementAt(i));
    } // if
  } // for
  // Copy back
  clear();
  for (int i = 0; i < save.size(); ++i) {
    addElement(save.elementAt(i));
  } // for
} // removeAllCopies

Analysis: The first loop is at most O(n) steps (which may include addElement()), the second loop is O(n) steps. Each addElement() takes O(1) steps, so it's an O(n) algorithm.


Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

Source text last modified Tue Jan 12 11:43:37 1999.

This page generated on Tue Jan 12 11:47:44 1999 by SiteWeaver.

Contact our webmaster at rebelsky@math.grin.edu