# Lecture 05: Amortized Analysis, Sets, Binary Search

Overview:

- Resizing arrays and amortized analysis.
- Sets: Disordered & order
- Binary Search

#### Strategies for Programming Success

- segment your time, often 5 * 2hr > 10hr.
- start early
- read assignment thoroughly
- skim readings
- skim provided codes

- work incrementally
- write a method, test and varify, then move on. (write a helper method if needed)

#### Running time of `Add(i, x)`

- n = current size of list
- assume size < capacity

What’s the running time of each line in `add(i, x)`

?

1
2
3
4
5
6
7
8

++size; // O(1)
Object cur = x; // O(1) since it is just assignment
for (int j = i, j < size; ++j) { // O(1) since it is just primary ops
Object next = contents[j]; // O(1) also assignment
contents[j] = cur; // O(1) also assignment
cur = next; // O(1) also assignment
}

a single iteration is constant and doesn’t depend on size. How many iterations: n - i + 1 overall running time is \((n - i + 1) O(1) + O(1) = O(n-i+1)\) This means adding at the end is more efficient than adding in the front.

#### Stack ADT

- push(x) –> add(size, x)
- peek() –> get(size - 1)
- pop() –> remove(size -1)

#### Running time of `increaseCapapcity()`

1
2
3
4
5
6
7

private class increaseCapacity(){
Object[] bigContents = new Object[capacity + 1]; // O(n+1)
for (int i = 0; i < capacity; ++i) {
bigContents[1] = contents[i]; // O(1)
}
}

The for loop: O(n) <– O(1) per iteration + n interations The whole method: \(O(n) = O(n+1) + O(n) + O(1)\)

#### Running time of `buildStack(stk, size)`

For loop:

- n interations
`stk.push(i)`

takes:- O(1) if no resize
- O(n) if resize (invoke
`increaseCapacity()`

)

Overall running time: \(n * O(n) = O(n^2)\) n = number of interations O(n) = time to resize

Although one operation may be expensive, when averaged across operations, overall cost is less expensive.

#### Amortized Analysis

Idea: Rather than paying worst-case cost for each op, average cost over all sequences of ops.

- e.g. expensive ops like resize are infrequent, so average add time is small.

Banker’s view: Each op has cost (running time)

- have an account $A$ that we can deposit to / withdraw from
- amortized cost of op is
- $ac(op) = cost(op) + bal(A’) - bal(A)$ bal = get balance of account

**Amortized cost is average cost per op over sequence of ops.**

**Example**. push op for stack, with capacity doubling
Suppose last resize was at size $N$, new capacity $2N$.

Total cost of next resize $C_{2N} = O(2N) = O(N)$

If N ≤ size < 2N, on push op:

- pay $O(1)$ for push without resize
- also put $\displaystyle \frac{1}{N} * C_{2N} = \frac{1}{N} * O(N) = O(1)$ in bank –> amortized cost of push op is \(ac(push) = O(1)\)

If size = 2N, on push op:

- pay push out pocket $O(1)$
- pay resize out account - decrease act by $C_{2N}$ –> amortized cost is \(\begin{aligned} ac(push) &= cost(push) + cost(resize) + \Delta A\\ O(1) &= O(1) + C_{2N} - C_{2N} \end{aligned}\)