CS 351 // ANALYSIS OF ALGORITHMS

Midterm Prep.

Proofs, recurrences, choosing a data structure, AVL trees, and heaps.

Lucas P. Cordova, Ph.D.

Willamette University  |  Fall 2026

O(n²)O(n)O(log n)

Outline

  1. The exam: what is on it and how it runs
  2. How to prove something: a framework
  3. Recurrences: a recipe that solves almost all of them
  4. Choosing a data structure: the question type that matters most
  5. AVL trees: review
  6. Heaps: review
  7. Sorts: review
  8. Wrap-up: how to use the week
CS 351
// SECTION

The exam

What is on it and how it runs

O(n²)O(n)O(log n)

The midterm at a glance

WhenWednesday, October 14, in class
CoversThe start of the course through heaps and heap sort
Not on itGreedy algorithms, minimum spanning trees, shortest paths
How you answerHandwritten, on the paper exam. No computers or other devices.
You may usePrinted copies of your own homework submissions and feedback, and of code you wrote for this course
You may not useAny device, LLMs or AI assistants of any kind, the internet, another person
WHAT TO EXPECT

Short analyses, traces by hand, one or two short proofs, and choose-a-data-structure problems, which carry the most weight.

CS 351
// SECTION

How to prove something

A framework

O(n²)O(n)O(log n)

What a proof is

  • A proof is an argument a skeptical reader can check, one step at a time.
  • Every step is either something given, a definition, or follows from earlier steps by a rule you can name.
  • Examples are not proofs. Checking n = 1, 2, 3 shows the claim is plausible, not that it is true.
  • You do not need to be clever. You need to pick the right template and fill it in.
THE FOUR MOVES, EVERY TIME
  1. Write the claim precisely.
  2. Pick the technique from the shape of the claim.
  3. Fill in that technique's template.  
  4. End by restating what you showed.
CS 351

Step 2: the shape of the claim picks the technique

If the claim looks likeUseTemplate
“For every n ≥ 0, …” or anything about a structure of size nInductionbase case, hypothesis, step
“This loop computes …”Loop invariantinitialization, maintenance, termination
“No … exists”, “… is always at least …”, “… must be …”Contradictionassume the opposite, derive something false
“This runs in O(…)”Count the worka sum for loops, a recurrence for recursion
“This claim is false”Counterexampleone specific input, shown to fail
ON THE EXAM

Name the technique in your first sentence: “We prove this by induction on h.” It tells the grader what to look for and it tells you what to write next.

CS 351

Induction: the template

  1. State P(n). Write the claim as a statement about one number n. Say what n measures (size, height, number of inserts).
  2. Base case. Show P holds for the smallest n. Actually check it.
  3. Inductive hypothesis. “Assume P(k) holds for some k ≥ base.” (Strong form: assume P(j) for every j ≤ k.)
  4. Inductive step. Show P(k + 1). Break the size k + 1 object into smaller pieces, apply the hypothesis to the pieces, and combine.
  5. Conclude. “By induction, P(n) holds for all n ≥ base.”
THE CHECK

Point to the exact line where you used the hypothesis. If you cannot find one, it is not an induction proof yet.

CS 351

Worked proof 1: induction

CLAIM

A perfect binary tree of height h has exactly 2h+1 − 1 nodes.

  1. P(h): a perfect binary tree of height h has 2h+1 − 1 nodes. Induction on h.
  2. Base, h = 0: a single node. 21 − 1 = 1. True.
  3. Hypothesis: assume a perfect tree of height k has 2k+1 − 1 nodes.
  4. Step: a perfect tree of height k + 1 is a root plus two perfect subtrees of height k. By the hypothesis each has 2k+1 − 1 nodes. Total: 1 + 2(2k+1 − 1) = 2k+2 − 1.
  5. Conclude: by induction, P(h) holds for every h ≥ 0. ■

HEIGHT 2: A ROOT PLUS TWO PERFECT TREES OF HEIGHT 1. 1 + 2(3) = 7.

CS 351

Loop invariants: the template

DEFINITION

A loop invariant is a statement that is true before every iteration of a loop. It describes what the loop has accomplished so far.

  1. State the invariant. “At the start of iteration i, …”
  2. Initialization. It is true before the first iteration.
  3. Maintenance. If it is true before an iteration, it is true before the next one.
  4. Termination. When the loop ends, the invariant plus the exit condition give you what you wanted.
IT IS INDUCTION IN DISGUISE

Initialization is the base case. Maintenance is the inductive step. Termination is the conclusion, with one extra idea: plug in the value the loop variable has when the loop stops.

CS 351

Worked proof 2: a loop invariant

1  def insertion_sort(A):
2      for i in range(1, len(A)):
3          key = A[i]
4          j = i - 1
5          while j >= 0 and A[j] > key:
6              A[j + 1] = A[j]
7              j -= 1
8          A[j + 1] = key
INVARIANT

At the start of iteration i, A[0:i] holds the original first i items, in sorted order.

  1. Initialization, i = 1: A[0:1] is one item. One item is sorted.
  2. Maintenance: assume A[0:i] is sorted. The inner loop shifts every item larger than key one place right and drops key into the gap. Now A[0:i+1] is sorted, which is the invariant for i + 1.
  3. Termination: the loop stops when i = n. The invariant says A[0:n] is sorted. That is the whole array. ■
CS 351

Worked proof 3: contradiction

CLAIM

In a max-heap with distinct keys, the largest key is at the root.

  1. Assume the opposite: the largest key sits at some node x that is not the root.
  2. x is not the root, so x has a parent p.
  3. The max-heap property says key(p) ≥ key(x). Keys are distinct, so key(p) > key(x).
  4. But key(x) is the largest key in the heap. Contradiction.
  5. So the assumption was false: the largest key is at the root. ■
TEMPLATE

Assume the opposite. Use definitions to push forward until two statements cannot both be true. Conclude the original claim.

CS 351

Five ways proofs fall short

  • Assuming what you are proving. If your step starts from P(k + 1), you are going backwards.
  • No base case, or a base case that is asserted and not checked.
  • The hypothesis is never used. Then the step is either wrong or not induction.
  • Proof by example. “It works for n = 4” proves the claim for n = 4.
  • Undefined letters. Say what n, k, and h are before you use them.
PARTIAL CREDIT

A correct template with a gap in the step earns most of the points. A page of algebra with no stated claim earns few. Write the skeleton first.

CS 351
// SECTION

Recurrences

A recipe that solves almost all of them

O(n²)O(n)O(log n)

Where a recurrence comes from

1  def merge_sort(A):
2      if len(A) <= 1:
3          return A
4      mid = len(A) // 2
5      L = merge_sort(A[:mid])     # one call, size n/2
6      R = merge_sort(A[mid:])     # one call, size n/2
7      return merge(L, R)          # O(n) work here
 
READ IT OFF THE CODE

T(n) = 2 T(n / 2) + n

SymbolMeaningHere
ahow many recursive calls one call makes2
bhow much smaller each subproblem is: size n / b2
f(n)work done in this call, outside the recursive callsn
  • Count the calls: that is a.
  • Look at the argument: that is n / b.
  • Everything else in the function body is f(n).
CS 351

The recipe: three steps

For T(n) = a T(n / b) + nd  (a ≥ 1, b > 1):

  1. Read off a, b, and d (the exponent of the work term).
  2. Compute the critical exponent c = logb a.
  3. Compare d with c.
CompareWho does the most workAnswer
d < cthe leavesΘ(nc) = Θ(nlogb a)
d = cevery level the sameΘ(nd log n)
d > cthe rootΘ(nd)
THIS IS THE MASTER THEOREM

One question: going down the tree, does the work grow (more subproblems) or shrink (smaller subproblems)?

CS 351

Who does most of the work?

For T(n) = a T(n / b) + Θ(nd), the comparison is between two exponents.

MeasuresIn words
c = logb ahow fast the number of subproblems growsgrowth caused by the recursion
dhow expensive the work at each node is, outside the recursive callsgrowth caused by the work at each node
THE QUESTION THE MASTER THEOREM ASKS

As I move down the recursion tree, does the total work per level get bigger, stay the same, or get smaller?

THE ANSWER TELLS YOU WHO WINS

Bigger: the leaves do most of the work. The same: every level contributes equally. Smaller: the root does most of the work.

CS 351

The recursion tree: root, levels, leaves

1  def find_max(A, lo, hi):          # max of A[lo:hi]
2      if hi - lo == 1:
3          return A[lo]
4      mid = (lo + hi) // 2
5      left = find_max(A, lo, mid)    # size n/2
6      right = find_max(A, mid, hi)   # size n/2
7      return max(left, right)        # O(1) work
  • Every box is one execution of the function, labelled with its range.
  • Root: the call you made, find_max(A, 0, 8).
  • Levels: the calls created by lines 5 and 6.
  • Leaves: calls on one item. They return on line 3 and make no calls.
  • Own work: line 7 (or line 3), 1 unit per call. So work per level = calls per level: 1, 2, 4, 8.
[0,8)root1 call[0,4)[4,8)level 12 calls[0,2)[2,4)[4,6)[6,8)level 24 calls[0,1)[1,2)[2,3)[3,4)[4,5)[5,6)[6,7)[7,8)leaves8 calls

find_max(A, 0, 8). TEAL: THE ROOT. AMBER: THE LEAVES.

CS 351

Leaves do most of the work: d < c

1  def find_max(A, lo, hi):          # max of A[lo:hi]
2      if hi - lo == 1:
3          return A[lo]
4      mid = (lo + hi) // 2
5      left = find_max(A, lo, mid)    # size n/2
6      right = find_max(A, mid, hi)   # size n/2
7      return max(left, right)        # O(1) work
root1 call, size n1level 12 calls, size n/22level 24 calls, size n/44level 38 calls, size n/88⋮leavesn calls, size 1n

Finding the maximum by halves: T(n) = 2 T(n / 2) + 1

  • Two calls, so a = 2. Each on half, so b = 2. One comparison outside the calls, so d = 0. c = log2 2 = 1. d < c.
  • Root: 1 call of size n. Its own work: 1.
  • Level 1: 2 calls, 1 unit each. Total 2. Level 2: 4 calls. Total 4.
  • Leaves: n calls on single items, 1 unit each. Total n.
  • Each level does twice the work of the one above.
THINK

Each call does almost nothing, but the calls multiply. The n leaves do more than everything above them: T(n) = Θ(nc) = Θ(n).

CS 351

Every level does the same work: d = c

1  def merge_sort(A):
2      if len(A) <= 1:
3          return A
4      mid = len(A) // 2
5      L = merge_sort(A[:mid])     # size n/2
6      R = merge_sort(A[mid:])     # size n/2
7      return merge(L, R)          # O(n) work here
root1 call, size nnlevel 12 calls, size n/2nlevel 24 calls, size n/4nlevel 38 calls, size n/8n⋮leavesn calls, size 1n

Merge sort: T(n) = 2 T(n / 2) + n

  • Two calls, so a = 2. Each on half, so b = 2. The merge is linear, so d = 1. c = log2 2 = 1. d = c.
  • Root: 1 call of size n. Its own work: n (the final merge).
  • Level 1: 2 calls, each merging n / 2 items. Total n.
  • Level 2: 4 calls, each merging n / 4 items. Total n.
  • Leaves: n calls on single items. Total n.
THINK

Twice as many calls, each doing half the work: it cancels exactly. No level wins, so count the levels: n work × log2 n levels = Θ(n log n).

CS 351

The root does most of the work: d > c

1  def search(A, k):               # binary search
2      if not A:
3          return False
4      mid = len(A) // 2
5      if A[mid] == k:
6          return True
7      if k < A[mid]:
8          return search(A[:mid], k)   # copies n/2
9      return search(A[mid + 1:], k)
root1 call, size nnlevel 11 call, size n/2n / 2level 21 call, size n/4n / 4level 31 call, size n/8n / 8⋮leaf1 call, size 11

Binary search that slices: T(n) = T(n / 2) + n

  • One call runs, so a = 1. On half, so b = 2. The slice copies about n / 2 items, so d = 1. c = log2 1 = 0. d > c.
  • Root: 1 call of size n. Its own work: n.
  • Level 1: 1 call of size n / 2. Work n / 2. Level 2: work n / 4.
  • Leaf: 1 call on an empty or one-item list. Work 1.
  • n + n/2 + n/4 + … is less than 2n. The root alone is more than half of it.
THINK

The work shrinks and the calls do not multiply. The root wins: T(n) = Θ(nd) = Θ(n). Passing lo and hi with no slicing gives Θ(log n).

CS 351

From code to a recurrence, step by step

 1  def blend(A, lo, hi):
 2      n = hi - lo
 3      if n <= 1:
 4          return A[lo] if n == 1 else 0
 5      h = n // 2
 6      q = h // 2
 7      x = blend(A, lo, lo + h)          # left half
 8      y = blend(A, lo + h, hi)          # right half
 9      z = blend(A, lo + q, lo + q + h)  # middle half
10      s = 0
11      for i in range(lo, hi):           # one pass
12          s += A[i]
13      return max(x, y, z, s)
  1. What is n? The size of the input this call works on. Here n = hi − lo.
  2. Base case. When n ≤ 1 the function returns at once: T(1) = Θ(1).
  3. Count the recursive calls that run in one execution. Three: x, y, z. So a = 3.
  4. How big is each call's input? Each range has length h = n / 2. So b = 2.
  5. Everything else is f(n). Cross out the recursive calls. What is left is a loop of n iterations plus constant work. So f(n) = n, and d = 1.
THE RECURRENCE

T(n) = 3 T(n / 2) + n

CS 351

Then solve it with the recipe

 1  def blend(A, lo, hi):
 2      n = hi - lo
 3      if n <= 1:
 4          return A[lo] if n == 1 else 0
 5      h = n // 2
 6      q = h // 2
 7      x = blend(A, lo, lo + h)          # left half
 8      y = blend(A, lo + h, hi)          # right half
 9      z = blend(A, lo + q, lo + q + h)  # middle half
10      s = 0
11      for i in range(lo, hi):           # one pass
12          s += A[i]
13      return max(x, y, z, s)

T(n) = 3 T(n / 2) + n    so a = 3, b = 2, d = 1.

  1. Critical exponent: c = logb a = log2 3.
  2. Estimate it with the definition of a log: 21 = 2 and 22 = 4, and 3 sits between them. So 1 < c < 2 (it is about 1.58).
  3. Compare: d = 1 is less than c. The leaves do most of the work.
  4. Answer: T(n) = Θ(nlog2 3), about n1.58.
SANITY CHECK

Two calls of size n / 2 with linear work would be n log n. A third call makes it worse, but it stays below n2. An answer between n log n and n2 is believable.

CS 351

Two traps when reading code

Trap 1: calls that are written but do not all run.

1  def search(A, lo, hi, k):
2      if lo >= hi:
3          return -1
4      mid = (lo + hi) // 2
5      if A[mid] == k:
6          return mid
7      if k < A[mid]:
8          return search(A, lo, mid, k)
9      return search(A, mid + 1, hi, k)
  • Two calls appear, but one execution makes one of them. a = 1, not 2.
  • T(n) = T(n / 2) + 1 = Θ(log n).

Trap 2: work hidden inside one line.

1  def search(A, k):
2      if not A:
3          return False
4      mid = len(A) // 2
5      if A[mid] == k:
6          return True
7      if k < A[mid]:
8          return search(A[:mid], k)
9      return search(A[mid + 1:], k)
  • A slice copies its elements: A[:mid] costs Θ(n).
  • T(n) = T(n / 2) + n = Θ(n). The same algorithm, no longer logarithmic.
CS 351

Six recurrences

Recurrence a b d c = logb a Compare T(n) Seen in
2T(n/2) + n 2 2 1 1 d = c Θ(n log n) merge sort
T(n/2) + 1 1 2 0 0 d = c Θ(log n) binary search
2T(n/2) + 1 2 2 0 1 d < c Θ(n) tree traversal
4T(n/2) + n 4 2 1 2 d < c Θ(n2)  
3T(n/3) + n2 3 3 2 1 d > c Θ(n2)  
2T(n/4) + √n 2 4 1/2 1/2 d = c Θ(√n log n)  
TRY IT
  1. T(n) = 8 T(n / 2) + n2
  2. T(n) = T(n / 3) + n.
CS 351

Logarithm rules 

RuleStatementUse it to
Definitionlogb x = y  means  by = xcompute c: log2 8 = 3 because 23 = 8
Productlog(xy) = log x + log ysplit log(n · n) into 2 log n
Quotientlog(x / y) = log x − log ysimplify log(n / 2) = log n − 1
Powerlog(xk) = k log xlog(n3) = 3 log n = Θ(log n)
Change of baselogb x = log x / log blog4 2 = 1/2; bases differ by a constant
Inverseblogb x = x2log2 n = n
Swapalogb n = nlogb acount leaves: 2log4 n = n1/2 = √n
TWO CONSEQUENCES

The base of a log does not matter inside Θ. And log(n!) = Θ(n log n), which is where the sorting lower bound comes from.

CS 351

When the recipe does not apply

The recipe needs subproblems of size n / b. If the size shrinks by subtraction, unroll or draw the tree.

RecurrenceUnroll itT(n)
T(n − 1) + 11 + 1 + … + 1, n timesΘ(n)
T(n − 1) + nn + (n − 1) + … + 1 = n(n + 1) / 2Θ(n2)
2T(n − 1) + 11 + 2 + 4 + … + 2nΘ(2n)
  • Recursion tree, in three questions: how much work on level i? How many levels? What do the levels sum to?
  • Geometric sums: shrinking terms sum to the first term, growing terms to the last term, equal terms to (one term) × (number of levels).
  • Arithmetic sum: 1 + 2 + … + n = n(n + 1) / 2 = Θ(n2).
CS 351
// SECTION

Choosing a data structure

The question type that matters most

O(n²)O(n)O(log n)

The method: five steps

  1. List the operations the problem needs. Underline the ones that happen most often.
  2. Classify each operation. Is it by key, by position, by order (next, previous, range, smallest), or only the extreme (max or min)?
  3. Write down the candidates. Usually three: one cheap and simple, one hash-based, one tree-based.
  4. Fill in the cost table. One row per candidate, one column per operation.
  5. Choose and justify. Name the operation that rules out each loser, and state the trade-off you accepted.
WHY THIS IS ON THE EXAM

This is the decision you make in real code. Nobody asks you to rotate a tree at work. They ask why the lookup is slow.

CS 351

Step 2: the questions that decide it

AskIf yesWhy
Do I only ever look up by exact key?hash tableexpected O(1), but no order at all
Do I need order: next larger, previous, range, sorted output?balanced BST (AVL)O(log n) for everything, order included
Do I only need the largest (or smallest), repeatedly?binary heapO(1) peek, O(log n) insert and remove, tiny and simple
Do I need items by position: the i-th, insert in the middle?array, or AVL as a sequencearray: O(1) access, O(n) middle insert; AVL: O(log n) both
Is the data static after loading?sorted arrayO(log n) search by binary search, nothing to maintain
Do I need a worst-case guarantee?avoid hashing and plain BSTshashing is expected-case; a plain BST can be O(n) tall
CS 351

The cost table to know cold

Structurefind(k)insertdeletemax or minnext larger
Unsorted arrayn1 (a)nnn
Sorted arraylog nnn1log n
Hash table1 (e)1 (e, a)1 (e)nn
AVL treelog nlog nlog nlog nlog n
Binary heapnlog n (a)log n (the max)1n

(a) amortized    (e) expected    All others are worst case.

READ THE TABLE BY ROWS

Hash table: fastest lookups, no order. AVL: nothing is fast, nothing is slow. Heap: one question answered very well.

CS 351

Worked example: the print shop

PROBLEM

A print shop keeps a set of job numbers. It needs add(k), cancel(k), and next_after(k): the smallest job number larger than k. Which structure?

  1. Operations: add, cancel, next_after. All three are frequent.
  2. Classify: add and cancel are by key. next_after is by order.
Candidateaddcancelnext_after
Hash table1 (e)1 (e)n
Sorted arraynnlog n
AVL treelog nlog nlog n
ANSWER

AVL tree. The hash table loses on next_after (no order, so it must scan). The sorted array loses on add and cancel (shifting). Trade-off accepted: O(log n) updates in place of the hash table's O(1).

CS 351

Three quick ones

The problem needsChooseThe deciding fact
An emergency room queue: add a patient with a priority, treat the most urgent next. Nothing else.Binary heapOnly the maximum is ever asked for. O(log n) both ways, O(1) to peek.
A course roster: look up a student by ID, enroll, drop. Never listed in order.Hash tableExact-key lookups only. Expected O(1), and order is not needed.
An event log: record an event with a timestamp, delete one, list every event between two times.AVL treeA range query is an order operation. Find the start in O(log n), then walk in order.
A text editor line buffer: jump to line i, insert a line in the middle, delete a line.AVL as a sequenceBy position, with middle inserts. An array pays O(n) per insert.
TRY IT

The roster now also needs “print all students in ID order” once a semester. Does the answer change?

CS 351

How to write the answer

  1. Name the structure in the first sentence.
  2. Give the cost of every operation the problem asked for, with (a) or (e) where it applies.
  3. Rule out the alternatives, one sentence each, naming the operation that sinks them.
  4. State the trade-off you accepted, if there is one.
  5. If the problem asks for it, describe how each operation works in two or three lines. Pseudocode or precise prose.
COMMON LOSSES

Choosing a hash table when the problem needs order. Forgetting that hashing is expected-case. Quoting O(log n) for a plain BST, which is only true if it is balanced.

CS 351
// SECTION

AVL trees

Review: the facts, the cases, one full insert

O(n²)O(n)O(log n)

AVL trees: what to know

  • Height: a leaf has height 0, an empty subtree −1. Skew(x) = h(right) − h(left).
  • AVL property: every node has skew −1, 0, or +1. It forces h ≤ 1.44 log2 n, so every operation is O(log n).
  • Insert or delete as in a BST, then walk up recomputing heights. At the lowest node with skew ±2, rotate.
skew(x)heavy child's skewShapeFix
+20 or +1same sidesingle: rotate_left(x)
+2−1zigzagdouble: rotate_right(x.right), then rotate_left(x)
−20 or −1same sidesingle: rotate_right(x)
−2+1zigzagdouble: rotate_left(x.left), then rotate_right(x)
INSERT VERSUS DELETE

An insert needs at most one fix (single or double). A delete can need a fix at every level on the way up.

CS 351

One full insert: add 22

  • The tree holds 30, 20, 40, 10, 25. Insert 22: it goes left of 25.
  • Walk up from the new leaf, recomputing heights.
  • skew(25) = −1. skew(20) = +1. skew(30) = −2.
  • Lowest unbalanced node: 30.
  • Its left child 20 has skew +1, the opposite sign. That is the zigzag shape.
  • Fix: rotate_left(20), then rotate_right(30).
3020102522403 (-2)2 (+1)0 (0)1 (-1)0 (0)0 (0)

LABELS: HEIGHT (SKEW). MAGENTA: UNBALANCED. AMBER: JUST INSERTED.

CS 351

After the double rotation

  • 25 was lifted to the root. 20 and 30 are its children.
  • 22 changed parents: it was 25's left child and is now 20's right child.
  • In-order before and after: 10, 20, 22, 25, 30, 40. Rotations never reorder keys.
  • Height went from 3 back to 2.
ON THE EXAM, WRITE FIVE THINGS

The lowest unbalanced node. Its skew. The shape (same side or zigzag). The rotation or rotations, by name. The final tree.

2520102230402 (0)1 (0)0 (0)0 (0)1 (+1)0 (0)

TEAL: THE NODE THE ROTATION LIFTED INTO PLACE.

CS 351
// SECTION

Heaps

Review: the array, the two moves, build

O(n²)O(n)O(log n)

Binary heaps: what to know

  • A complete binary tree stored in an array, level by level. No pointers.
  • parent(i) = (i - 1) // 2
  • left(i) = 2i + 1, right(i) = 2i + 2
  • Max-heap property: every parent is at least its children. The max is at index 0.
  • It is not a BST: there is no order between siblings.
  • Height is ⌊log2 n⌋, always.
972584[0][1][3][4][2][5]

THE ARRAY [9, 7, 8, 2, 5, 4] AS A TREE. LABELS ARE INDICES.

COSTS

insert and delete_max: O(log n) each.   build: O(n).   Heap sort: O(n log n), in place.

CS 351

Build and delete_max, by hand

build on [2, 9, 4, 7, 5, 8]: sink down at index 2, then 1, then 0.

StepWhat happensArray
start2 9 4 7 5 8
i = 24 has one child, 8. Swap.2 9 8 7 5 4
i = 19 beats 7 and 5. Nothing.2 9 8 7 5 4
i = 02 swaps with 9, then with 7.9 7 8 2 5 4
  • delete_max: swap 9 with the last item (4), pop the 9, sink the 4. It swaps with 8. Result: [8, 7, 4, 2, 5].
  • Sink down swaps with the larger child. Swapping with the smaller one breaks the property.
WHY BUILD IS O(n)

Sinking costs a node's height. Half the nodes have height 0, a quarter have height 1, and so on. The total is O(n).

CS 351
// SECTION

Sorts

Review: one table, then which one when

O(n²)O(n)O(log n)

Every sort so far, in one table

SortBestWorstIn placeStableOne-line idea
Insertionnn2yesyesgrow a sorted prefix, one item at a time
Selectionn2n2yesnorepeatedly move the max to the end
Mergen log nn log nnoyessort two halves, merge them
Heapn log nn log nyesnobuild a heap, delete_max n times
AVLn log nn log nnoyesinsert everything, traverse in order
Countingn + kn + knoyeskeys in 0..k: count, then place
Radixn logn kn logn knoyescounting sort on each base-n digit
THE LOWER BOUND

Any sort that only compares keys needs Ω(n log n) comparisons in the worst case. Counting and radix sort are not comparison sorts: they use keys as array indices.

CS 351

Which sort when

SituationReach forBecause
Small input, or nearly sortedinsertion sortO(n) on sorted input, O(nk) when items are at most k places off
Need a worst-case guarantee and no extra memoryheap sortO(n log n) always, in place
Need stability (equal keys keep their order)merge sortstable and O(n log n), at the price of O(n) extra space
Integer keys in a small rangecounting or radix sortlinear time, by not comparing
Data arrives over time and you query in betweenAVL treeit is a sort you can keep inserting into
TRY IT

Sort n integers, each between 0 and n3 − 1, in O(n). Which sort, and why does it not break the lower bound?

CS 351
// CLOSING

Wrap-up

How to use the week

O(n²)O(n)O(log n)

Between now and the exam

  • Work the study guide's sample problems on paper, timed, about 12 minutes each.
  • Rebuild the cost table for the five structures from memory, with a reason for each cell.
  • Write out the three templates once: induction, loop invariant, contradiction.
  • Redo one homework problem of each kind from a blank page, then compare with the posted solution.
  • Trace by hand: an AVL insert with a double rotation, a heap build, one recurrence from each of the three cases.
THE ONE-LINE VERSION

Templates for proofs, a recipe for recurrences, a five-step method for choosing a structure. The exam rewards method over memory.

CS 351

References

  1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms, 4th ed. MIT Press, 2022. Section 2.1 (loop invariants), Sections 4.3 to 4.5 (recurrences and the master method), Chapter 6 (heapsort), Chapter 8 (sorting in linear time), Chapters 11 to 13 (hash tables and search trees).
  2. The CS 351 Midterm Study Guide: topics, what you may use, and sample problems.
CS 351

CS 351 F26: Midterm Prep

By Lucas Cordova

Private

CS 351 F26: Midterm Prep

Midterm preparation for Fall 2026