Algorithms and OOD (CSC 207 2014S) : Assignments

Assignment 5: Algorithm Design and Analysis


This assignment is currently in draft form.

Due: 10:30 p.m., Wednesday, 5 March 2014

Summary: In this assignment, you will develop and analyze a variety of small programs.

Purposes: The focus of this assignment is on building your skills in designing, constructing, and testing algorithms along with loop invariants.

Collaboration: You may work alone or in a group of size two, three, or four.

Submitting: Please put all of your work in a GitHub repository named csc207-hw5. Email the address of that repository to grader-207@cs.grinnell.edu. Please use a subject of “CSC207 2014S Assignment 5 (Optional: Your Name)”.

Warning: So that this assignment is a learning experience for everyone, we may spend class time publicly critiquing your work.

Preparation

Create a new Eclipse project for this assignment. You can name the project whatever you like, provided it's not in bad taste.

Assignment

Part A: Recurrence Relations

In class, we've looked at a few recurrence relations that model the running time of algorithms and informally found their “closed form” (non-recursive) solution. Using similar techniques, find the closed form of the following recurrence relations.

  • f0(1) = 1. f0(n) = n + f0(n/2).
  • f1(1) = 1. f1(n) = n + f1(n-1).
  • f2(1) = 1. f2(n) = c + f2(n-1), for some constant c.
  • f3(1) = 1. f3(n) = c + f3(n/2), for some constant c.
  • f4(1) = 1. f4(n) = n + 2*f4(n/2).
  • f5(1) = 1. f5(n) = c + 2*f5(n/2), for some constant c.
  • f6(1) = 1. f6(n) = c + 2*f6(n-1), for some constant c.

Part B: The Dutch National Flag

The Dutch National Flag problem is a statement of an approach to dividing a collection of values into three categories. Given an array of values, vals, and a mechanism for classifying the values as “red” (small, belonging on the left), “white” (medium, belonging in the middle), or “large” (large, belonging on the right), rearrange the values so that the red values come first, then the white values, and then the blue values.

Of course, in order to classify values, we need a classifier. Here's a useful interface.

/**
 * Objects that classify other objects.  Useful for DNF.
 */
public interface Classifier<T>
{
  /**
   * Classify val into one of three categories, which we call
   * "red", "white", and "blue" for convenience.  If val is red,
   * returns a negative number.  If val is white, returns zero.
   * If val is blue, returns a positive number.
   */
  public int classify(T val);
} // interface Classifier

Since dealing with generics can be difficult, we'll focus on the subproblem of rearranging arrays of strings. Ideally, we can think of the classifier like this.

/**
 * Objects that classify strings.  Useful for DNF.
 */
public interface StringClassifier
  implements Classifier<String>
{
} // interface StringClassifier

But that's probably a little too sophisticated. So you can use the following definition of the interface.

/**
 * Objects that classify strings.  Useful for DNF.
 */
public interface StringClassifier
{
  /**
   * Classify val into one of three categories, which we call
   * "red", "white", and "blue" for convenience.  If val is red,
   * returns a negative number.  If val is white, returns zero.
   * If val is blue, returns a positive number.
   */
  public int classify(String val);
} // interface StringClassifier

Implement the following method.

/**
 * Rearrange vals so that red values precede white values and white values
 * precede blue values.
 *
 * @post
 *   Exist P and Q, 0 <= P <= Q <= vals.length, s.t.
 *     For all i, 0 <= i < P, classifier.classify(vals[i]) < 0
 *     For all i, P <= w < Q, classifier.classify(vals[i]) == 0
 *     For all i, Q <= i < vals.length, classifier.classify(vals[i]) > 0
 *   Values have neither been added to nor removed from vals; the new
 *     vals is a permutation of the original.
 */
public void dnf(String[] vals, StringClassifier classifier)
{
  // STUB
} // dnf(String[], StringClassifier) 

You may recall that we've identified a useful invariant for this problem. You should use this invariant, or a similar one.

+---------+---------+---------+---------+
|   red   |  white  | unknown |   blue  |
+---------+---------+---------+---------+
|         |         |         |         |
0         r         i         b         length

Here's a simple classifier.

/**
 * Classify strings as red (fewer than 5 characters), white (5
 * characters), or blue (more than 5 characters).
 */
public class SimpleStringSizeClassifier
  implements StringClassifier
{
  public int classify(String val)
  {
    return val.length() - 5;
  } // classify
} // class SimpleStringClassifier

Here's another classifier. This one identifies strings based on their first character.

/**
 * Classify strings as red (starts with a lowercase letter), white
 * (starts with an uppercase letter), or blue (starts with anything
 * else, or is empty).
 */
public class ClassifyStringsByFirstCharacter
  implements StringClassifier
{
  public int classify(String val)
  {
    // Empty strings don't start with uppercase or lowercase letters,
    // and are therefore blue.
    if (val.length() == 0)
      return 1;
    // Strings that start with a lowercase letter are red.
    else if (Character.isLowerCase(val.charAt(0)))
      return -1;
    // Strings that start with an uppercase letter are white.
    else if (Character.isUpperCase(val.charAt(0)))
      return 0;
    // Everything else is blue
    else
      return 1;
    } // classify(String)
} // ClassifyStringsByFirstCharacter

Part C: Skip Lists

Skip Lists are a data structure that is useful for cases in which you have a collection of values, want to be able to expand and shrink the collection, and want to be able to find values in the collection. You can learn more about skip lists from the handout.

Implement the following class.

/**
 * Skip lists of strings, stored alphabetically.
 */
public class SkipListOfStrings
  implements SetOfStrings
{
  . . .
} // class SkipListOfStrings

As the declaration suggests, your class should implement a simple set interface. Here is that interface.

/**
 * Simple sets of strings.
 */
public interface SetOfStrings
{
  /**
   * Determine if the set contains a particular string.
   */
  public boolean contains(String str);

  /**
   * Add an element to the set.
   *
   * @post contains(str)
   */
  public void add(String str);

  /**
   * Remove an element from the set.
   *
   * @post !contains(str)
   */
  public void remove(String str);
} // interface SetOfStrings

You may find it useful to write loop invariants for the three methods, but you are not required to do so.

Part D: Exponentiation

As you may recall, we can write an efficient (well, O(log2n)) exponentiation algorithm by relying on the following rules.

  • x0 = 1.
  • x2k = xk * xk, for k > 0.
  • xk+1 = x * xk, for even k >= 0.

Using invariants to help you design the algorithm, write an iterative O(log2n)) exponentiation algorithm by using those rules.

  /**
   * Compute x^n.
   *
   * @pre n >= 1.
   */
  public double expt(double x, int n)
  {
  } // expt(double, int)

Hint: You may find it useful to keep track of two intermediate values, one of which only takes on values of x2k and one of which holds the product of any other x's you need in the result.

Part E: Binary Search

a. Implement the following procedure, using a divide-and-conquer strategy.

/**
 * Search for val in values, return the index of an instance of val.
 *
 * @param val
 *   An integer we're searching for
 * @param values
 *   A sorted array of integers
 * @result
 *   index, an integer
 * @throws Exception
 *   If there is no i s.t. values[i] == val
 * @pre
 *   values is sorted in increasing order.  That is, values[i] <
 *   values[i+1] for all reasonable i.
 * @post
 *   values[index] == val
 */
public static int binarySearch (int i, int[] vals) 
  throws Exception 
{
  return 0;   // STUB
} // binarySearch

b. Evidence suggests that (i) many programmers have difficulty implementing binary search correctly and (ii) many programmers do only casual testing of their binary search algorithm. But it's really easy to write a relatively comprehensive test suit for binary search. (The test suite is based on one by Jon Bentley.)

For each s from 1 to 32
    Create an array of size s, containing the values 0, 2, 4, ... 2*(s-1)
    For all i from 0 to s-1, inclusive
        // Make sure that value 2*i is in position i
        assert(binarySearch(2*i, array) == i)
        // Make sure that odd values are not in the array
        assertException(binarySearch(2*i+1, array))
    assertException(-1, array)

Implement this test. Then repair any bugs you find in your implementation of binary search.

Citations

This assignment grew from exercises in class and some discussions with students.

The test for binary search is based on one from a Programming Pearl by John Bentley.

Copyright (c) 2013-14 Samuel A. Rebelsky.

Creative Commons License

This work is licensed under a Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.