# Class 21: Algorithm Analysis, Revisited

Back to Lab: Recursion. On to Arrays.

Held Monday, February 28, 2000

Overview

Today we revisit the notion of Big-O analysis and analyze the running time of some recursive and non-recursive algorithms.

Notes

• Exam 1 returned. We'll spend some time going over the exam.
• Thanks for your patience with me away. The news is basically good: my mother does not have cancer. However, she's still having trouble breathing and is still in the Respiratory ICU.
• I received over 200 email messages while I was away, so it will take some time to get to any email you sent.
• I printed some handouts before I left. Unfortunately, the service bureau left them in my office rather than in the department office, so I assume you never got them. You'll get them today.
• Because of my absence, I need to revise the syllabus somewhat. Expect changes over the next few days.
• What did you think about the film?
• What did you learn last week?
• Assignments:

Contents

Summary

• Some recursive algorithms
• Binary search
• Efficient exponentiation
• Coputing Fibonacci numbers
• Some techniques for analyzing those algorithms

## Notes on Exam 1

### General

• I do not report minimum and maximum grades on exams. Since I don't curve grades, they should not matter.
• I would prefer that you not discuss your grades with other students. I find that such discussions only make people with lower grades feel inappropriately bad and people with higher grades feel inappropriately good.
• If you got less than an 80 on the exam, you should probably talk to me. There's probably some key points that you aren't understanding, and we need to resolve them sooner rather than later.

### Formatting If Statements

Please format nested `if` statements as follows:

```if (test1) {
statements1
} // if (test1)
else if (test2) {
statements2
} // if (test2)
...
else if (testn) {
statementsn
} // if (testn)
else {
default
} // none of the above
```

That is, put the `else` and subsequent `if` on the same line and use the same level of nesting. This makes it easier to fit lots of conditions in a reasonable amount of space.

### X and Y Coordinates

I'll draw a picture to illustrate the meaning of X and Y coordinates. All of you got it wrong.

### Moving Pieces

A number of you had trouble finding statistically valid ways to move pieces. In particular, the typical solution was something like this:

```for horizOffset = -1 to 1
for vertOffset = -1 to 1
if (horizOffset != 0) || (vertOffset != 0) {
int newx = this.x + horizOffset;
int newy = this.y + vertOffset;
if (!board.occupied(newx,newy)) {
this.set(newx,newy);
board.updatedLocation(this);
return;
} // if the space is free
} // if at least one offset is nonzero
```

Unfortunately, this type solution is more likely to move the piece to particular locations than others. This particular solution will tend to move pieces to the left and downwards.

A second solution was to repeatedly choose a random number between 1 and 8 and check if the ``corresponding space'' was free. You keep choosing and checking until a free space is found. This is inefficient. It will also run forever if there are no free spaces around.

The best solution is to count the number of available spaces and choose a random number between 1 and that number. You can then use the corresponding space.

## Running Times of Recursive Algorithms

• We'll begin by investigating a few recursive algorithms, including binary search, exponentiation, and various forms of computing values in the Fibonacci sequence.
• Binary search: find something in a sorted collection
• Exponentiation: compute xn
• Fibonacci: determine the nth element of the Fibonacci sequence 1, 1, 2, 3, 5, 8, 13, 21, ...
• While you know some techniques for computing the running times of iterative algorithms, you don't know about ways to compute the running times of recursive algorithms.
• It is possible to rephrase every recursive algorithm iteratively.
• However, it may be easier to directly analyze the running time of a recursive algorithm.

## Binary Search

• You learned binary search in 151 (or at least I think you did). We'll review it quickly and look at some analysis.
• To find something in a sorted collection:
• Identify the middle element of the collection
• If that's the thing you're looking for, you're done.
• If that thing is larger than what you're looking for, recurse on the subcollection of smaller elements.
• If that thing is smaller than what you're looking for, recurse on the subcollection of larger elements.
• What's the running time? How do you know?

## Exponentiation

• There are a number of techniques one can use for computing xn, where n is an integer.
• Complicated mathematics
• Multiply x*x*x*...*x n times
• Divide and conquer
• Although our algorithms will admit negative exponents, our analyses will assume positive exponents.
• As we observed, there are two techniques for doing the repeated multiplication: we can use iteration or recursion.
• Let's consider each function
```
/**
* An illustration of different mechanisms for computing x^n.
*
* @author Samuel A. Rebelsky
* @version 1.0 of October 1999
*/
public class Exponentiation {

// +---------+-------------------------------------------------
// | Methods |
// +---------+

/**
* Compute x^n by multiplying x*x*...*x n times.  Uses
* iteration as the mechanism for repetition.
*/
public static double expA(double x, int n) {
// Handle negative exponents.  x^(-n) = 1/(x^n)
if (n < 0) {
return 1/expA(x,-n);
}
// Prepare to compute the product.
double product = 1;
// Multiply by each x.  (Note that 1*x*x*...*x = x*x*...*x.)
for (int i = 0; i < n; ++i) {
product = product * x;
} // for
// That's it, we're done.
return product;
} // expA(double,int)

/**
* Compute x^n by multiplying x*x*...*x n times.  Uses
* recursion as the mechanism for repetition.
*/
public static double expB(double x, int n) {
// Handle negative exponents.  x^(-n) = 1/(x^n)
if (n < 0) {
return 1/expB(x,-n);
} // if (n < 0)
// Base case: x^0 is 1.
if (n == 0) {
return 1;
} // if (n == 0)
// Recursive case: x*x*...*x = x*(x*...*x)
// That is: x^n = x*(x^(n-1))
else {
return x * expB(x, n-1);
} // Recursive case, if (n > 0)
} // expB(double,int)

/**
* Compute x^n by a recursive divide-and-conquer algorithm.
*/
public static double expC(double x, int n) {
// Handle negative exponents.  x^(-n) = 1/(x^n)
if (n < 0) {
return 1/expB(x,-n);
} // if (n < 0)

// Base case: x^0 is 1.
if (n == 0) {
return 1;
} // Base case: n == 0

// Recursive case (when n is odd)
//   x*x*...*x = x*(x*...*x)
//   That is: x^n = x*(x^(n-1))
else if (n % 2 == 1) {
return x * expC(x,n-1);
} // Recursive case (when n is odd)

// Recursive case (when n is even)
//   Let n be 2k
//   x^n = x^(2k) = x^k * x^k
else {
int k = n/2;
double tmp = expC(x,k);
return tmp*tmp;
} // Recursive case (when n is even)
} // expC
} // class Exponentiation

```

### Repeated Multiplication with Iteration

• The first version, `expA`, is perhaps the easiest to analyze.
• There is some preparatory work. That takes a constant number of steps. We'll call that a1.
• There is a loop that is repeated n times.
• Each time through the loop, a constant number of steps are done. We'll call that a2.
• After the loop, a few more steps are done (okay, just a return). We'll call that a3.
• What's the approximate running time?
• a1 + a2*n + a3
• Using Big-O notation, we can throw away the constants and get O(n).

### Repeated Multiplication with Recursion

• The second version, `expB`, is somewhat more difficult to analyze because of the recursion.
• Stay tuned for more details!

## History

Tuesday, 18 January 2000

• Created as a blank outline.

Monday, 28 February 2000

Tuesday, 29 February 2000

Back to Lab: Recursion. On to Arrays.

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.