Assignment 3: Algorithm Analysis

Assigned: Friday, March 3, 2000
Due: Friday, March 10, 2000

Preliminaries

While you may discuss this assignment with other students, and even work out solutions together, I would prefer if each student wrote up his or her own solutions.

You may find the following programs helpful for problems 2 and 3:

1. Big-O Computation

Bound the running time using Big-O notation of each of the following algorithms. Try to make the bound as close as possible. Note that you may have to come up with an appropriate metric for the size of each problem.

(a) Smallest Value

```To find the smallest value in a collection ...
Set smallestSoFar to one value in the collection
While unchecked values remain in the collection
Pick one
If that value is smaller than guess then
Set smallestSoFar to that value
Return smallestSoFar
```

(b) Smallest Value Using Divide and Conquer

```To find the smallest value in a collection ...
If the collection is empty then
Report an error
Otherwise, if the collection has only one value then
Return that value
Otherwise
Split the collection into two parts
Find the smallest value in each part
Take the minimum of those two values
```

(c) Least Difference

```To find the smallest difference between any two
different numbers in a collection ...
List all pairs of different numbers in the collection
Set estimate to
the difference between the values in the first pair
For each remaining pair
Compute the difference between the two values
If that differences is less than estimate then
Set estimate to that difference
Return estimate
```

(d) Least Difference, Revisited

```To find the smallest difference between any two
different numbers in a collection ...
Set estimate to ``infinity''
For each value, v, in the collection
For each value, u, not equal to v
If |u-v| < estimate then
Set estimate to |u-v|
Return estimate
```

2. Computing Square Roots with Divide and Conquer

Here's an iterative algorithm that finds the square root of a value to a specified accuracy.

```/**
* Compute the square root of val to accuracy 1/n.
* Pre: (1) val >= 0
*      (2) n > 0
*      (3) The square root of val can be represented.
* Post: (1) Returns a value v such that |v-sqrt(val)| < 1/n.
*/
public static double sqrt(double val, long n) {
// Compute 1/nth of the range of possible values
double accuracy = 1.0/((double) n);
double guess = 0;
// Determine the difference between the square of the guess
// and the actual square root.
double diffsquared = val;

// Step through all the possible values between 0 and val.
for (double nextguess = 0.0;
nextguess <= val;
nextguess = nextguess + accuracy) {
// Compute the difference between the square of the guess
// and the actual value.
double nextdiff = Math.abs(nextguess*nextguess-val);
// If it's better, use it.
if (nextdiff < diffsquared) {
guess = nextguess;
diffsquared = nextdiff;
}
} // for
return guess;
} // sqrt(double,double)
```

As you might be able to tell, what this algorithm basically does is divide the interval between 0 and val into potential guesses that occur every 1/n units. It then tries each of these guesses and uses the best. Why 0 and val? Because we know that the square root of val is at least 0 and no more than val.

The running time of this algorithm is somewhat strange. It's based on both n and val. More or less, this algorithm is O(n*val).

(a) Revised algorithm

Make the algorithm more efficient by using a divide-and-conquer strategy. You need not write working Java code, although you will receive a modicum of extra credit for writing working code.

(b) Running time

Determine the running time of the revised algorithm.

3. Stamp Purchasing with Dynamic Programming

[This problem is based on a similar problem discussed in Duane Bailey's Java Structures.]

Suppose we have a package to send. We know how much it will cost to send the package. We'd like to minimize the number of stamps to purchase in order to send the package without paying extra. For example, if the package costs \$0.40 to mail and the post office sells \$0.50, \$0.33, \$0.20, \$0.05, and \$0.01 stamps, we'd purchase two \$0.20 stamps (even though one \$0.50 stamp would require fewer stamps).

Here's an approximate algorithm to determine the number of stamps we need. We assume that it is always possible to come up with a combination of stamps, no matter what value. (For example, we disallow the case when we want \$0.07 and only \$0.02 stamps are available.)

Our strategy is to see which stamp is best to buy now, and then go on (more or less). How do we know which is best to buy know? We recursively check how many steps it would cost if we bought each stamp and take the minimum of those numbers.

```/**
* Compute the minimum number of stamps that exactly
* totals val cents.
* Pre: (1) val >= 0
*      (2) All stamps values are positive.
*      (3) It is possible to combine stamps for any value.
* Post: Returns N such that it is possible to buy N stamps
*   for exactly val and it is not possible to buy M < N
*   stamps for exactly val.
*/
public static int minimumStamps(
int val,
int[] stampValues)
{
// Base case: You need no stamps for 0 cents.
if (val == 0) {
return 0;
}
// Recursive case: Minimize alternatives
else {
int i = 0; // An index into stampValues
int guess; // Our best guess so far as to the minimum.
int nextGuess;  // Another guess to try

// Find the first stamp that's still worth buying.
while (stampValues[i] > val)
i++;

// We might buy that stamp.
int guess = 1 + minimumStamps(val-stampValues[i], stampValues);

// But we might also buy other stamps.
for (i = i+1; i < stampValues.length; i++) {
if (stampValues[i] <= val) {
nextGuess =
1 + minimumStamps(val-stampValues[i], stampValues);
if (nextGuess < guess) {
guess = nextGuess;
}
} // if the stamp is worth buying.
} // for

// That's it, we're done
return guess;
} // recursive case
} // minimumStamps(int, int[])
```

(a) Informal analysis

Augment `Stamps.java` so that it counts the number of calls (recursive and otherwise) to `minimumStamps`. How many calls are made when you compute the number of stamps for 1, 5, 8, 10, 15, 20, 35, 40, 50, and 53 cents?

(b) Improving the Algorithm

As you may have noticed, this algorithm gets very slow as val gets large. Using the technique of caching smaller results in a table (a technique we call dynamic programming) that we used with such success in the box-packing and Fibonacci algorithms, rewrite this algorithm. You need not write working code, but it would be nice if you did.

History

Wednesday, 1 March 2000

• Created.

Thursday, 2 March 2000

Monday, 6 March 2000

• Corrected confusing sentence.

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.