Lecture 05: Amortized Analysis, Sets, Binary Search

Overview:

  1. Resizing arrays and amortized analysis.
  2. Sets: Disordered & order
  3. 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}\)