Part IV · Design Techniques Chapter 15

Greedy Algorithms

Take the locally best option, never look back, and sometimes end up with the globally best answer — but only when the problem has a property you must prove.

A greedy algorithm makes the choice that looks best right now and commits to it, never reconsidering. That is dramatically cheaper than dynamic programming, which considers every choice and takes the best after the fact. The catch is that greedy is usually wrong. This chapter is really about the one property — the greedy-choice property — that separates the problems where greed works from the many where it does not, and how to prove you are in the first category.

4th edition note. Numbering shifted from 16 to 15. The biggest content change: the 3rd edition’s section on matroids has been removed, replaced by a new section on offline caching. Activity selection, elements of the greedy strategy, and Huffman codes are retained.

Contents

  1. Activity selection
  2. Proving the greedy choice is safe
  3. Elements of the greedy strategy
  4. Greedy versus dynamic programming
  5. Huffman codes
  6. Offline caching
  7. Recap

Activity selection

n activities compete for one resource. Activity i has start time sᵢ and finish time fᵢ. Two activities are compatible if their intervals do not overlap. Select a maximum-size set of mutually compatible activities.

The greedy choice: always take the activity that finishes first. Intuition — finishing early leaves the resource free for the longest possible remaining time, which can only help. Assume the activities are sorted so that f₁ ≤ f₂ ≤ … ≤ fₙ.
GREEDY-ACTIVITY-SELECTOR(s, f, n) 1 A = {a₁} // first to finish is always in 2 k = 1 3 for m = 2 to n 4 if s[m] ≥ f[k] // starts after the last selected finishes 5 A = A ∪ {aₘ} 6 k = m 7 return A

Θ(n) after sorting, so Θ(n lg n) overall — a single pass. Compare with a DP formulation of the same problem, which is Θ(n³).

0481216 a₁ a₂ a₃ a₄ a₅ a₆ sorted by finish time green = selected each pick is the first that starts after the previous one ends
Figure 15.1 — Greedy activity selection. The dashed lines mark where the resource frees up.
Other plausible greedy rules that fail. Picking the shortest activity first fails: one short activity can block two longer compatible ones. Picking the one with fewest conflicts fails on a constructible counterexample. Picking the earliest starting fails badly — one activity spanning the whole day would be chosen and block everything. Earliest finish time is the one that works, and the fact that three sensible alternatives fail is precisely why you must prove it.

Proving the greedy choice is safe

The standard proof shape, used for every greedy algorithm in the book:

Exchange argument. Take any optimal solution. Show it can be transformed, without loss, into one that contains the greedy choice. Therefore some optimal solution contains the greedy choice, so making it is safe.

Theorem 15.1 for activity selection. Let Sₖ be a subproblem and aₘ the activity in it with earliest finish time. Then aₘ is in some maximum-size subset of compatible activities of Sₖ.

Proof. Let Aₖ be a maximum-size compatible subset, and let aⱼ be its earliest-finishing member. If aⱼ = aₘ, done. Otherwise consider Aₖ′ = Aₖ - {aⱼ} ∪ {aₘ}. Since fₘ ≤ fⱼ and everything else in Aₖ starts after fⱼ, everything still fits. Aₖ′ has the same size and is compatible, so it is also optimal — and it contains aₘ.

Note what this buys: after making the greedy choice, only one subproblem remains. There is no need to try alternatives, so no table, no memoisation, and the algorithm is a single loop.

Elements of the greedy strategy

Two properties are required, and the first is the one that distinguishes greedy from DP.

1. Greedy-choice property. A globally optimal solution can be arrived at by making a locally optimal (greedy) choice. The choice may depend on choices already made, but not on any future choice or on the solutions to subproblems. This is what lets the algorithm proceed top-down, making one choice and reducing to one subproblem.
2. Optimal substructure. The same property dynamic programming needs: an optimal solution contains optimal solutions to subproblems.

The chapter’s design procedure:

1. Cast the problem as one where you make a choice and are left with ONE subproblem. 2. Prove there is always an optimal solution making the greedy choice, so the choice is always safe. 3. Show that combining the greedy choice with an optimal solution to the remaining subproblem gives an optimal solution overall.

Greedy versus dynamic programming

Both need optimal substructure, so the substructure alone does not tell you which to use. The classic pair of problems makes the difference sharp.

Fractional knapsack0-1 knapsack
RulesYou may take any fraction of an itemEach item is taken whole or not at all
MethodGreedy: sort by value per pound, take the best until fullDynamic programming over capacity
CostΘ(n lg n)Θ(nW)
WhyThe last item can be split to fill the knapsack exactly, so no capacity is ever wastedLeftover capacity may be wasted, and whether that is acceptable depends on later items — the greedy-choice property fails
The 0-1 counterexample worth remembering. Knapsack capacity 50. Item 1: 10 lb, $60 ($6/lb). Item 2: 20 lb, $100 ($5/lb). Item 3: 30 lb, $120 ($4/lb). Greedy by value density takes item 1 first, then item 2, then cannot fit item 3: total $160 with 20 lb wasted. The optimum is items 2 and 3: $220. Taking the densest item first was a mistake that only became visible later — exactly the failure of the greedy-choice property.
The test in one sentence: if the best first choice can only be identified by knowing how the rest turns out, greedy fails and you need dynamic programming.

Huffman codes

A compression problem. Given characters with frequencies, assign each a binary codeword to minimise the total encoded length. Codes must be prefix-free — no codeword is a prefix of another — so decoding is unambiguous and needs no separators.

A prefix-free code corresponds exactly to a binary tree whose leaves are the characters: left is 0, right is 1, and the codeword is the root-to-leaf path. The cost of a tree T is B(T) = ∑ c.freq · dᴛ(c), the frequency-weighted sum of depths. An optimal code corresponds to a full binary tree, one where every internal node has two children.
HUFFMAN(C) 1 n = |C| 2 Q = C // min-priority queue keyed on freq 3 for i = 1 to n - 1 4 allocate a new node z 5 z.left = x = EXTRACT-MIN(Q) // two rarest 6 z.right = y = EXTRACT-MIN(Q) 7 z.freq = x.freq + y.freq 8 INSERT(Q, z) 9 return EXTRACT-MIN(Q) // the root

The greedy choice: merge the two lowest-frequency characters. With a binary min-heap from Chapter 6, this is O(n lg n)n-1 iterations, each doing three O(lg n) heap operations.

100 a:45 55 b:13 30 c:12 d:18 01 01 01 a = 0 b = 10 c = 110 d = 111 frequent → short rare → long
Figure 15.2 — A Huffman tree. Merging the two rarest nodes repeatedly pushes rare characters deepest, which is exactly where the long codewords are.

Why the greedy choice is safe (Lemma 15.2). Let x and y be the two lowest-frequency characters. There exists an optimal prefix code in which they are siblings at maximum depth. The exchange argument: take an optimal tree, find the two deepest siblings, and swap them with x and y. Since x and y have the lowest frequencies, moving them deeper and the others shallower cannot increase the weighted cost.

Lemma 15.3 supplies the optimal substructure: replacing x and y by a merged character of combined frequency yields a smaller problem whose optimal solution extends to an optimal solution of the original. Together the two lemmas give Theorem 15.4: HUFFMAN produces an optimal prefix code.

Offline caching

New in the 4th edition, and it makes a nice bookend with Chapter 27.

A cache holds k blocks. A sequence of n block requests arrives. On a cache miss the block must be fetched, and if the cache is full something must be evicted. Minimise the number of misses. In the offline version you know the entire request sequence in advance.

The greedy rule: evict the block whose next access is furthest in the future. This is Belady’s rule, also called the furthest-in-future strategy, and it is provably optimal for offline caching. The proof is an exchange argument on the sequence of evictions.
And it is unimplementable in reality. A real cache does not know the future. Belady’s rule is therefore a theoretical benchmark, not an algorithm you can deploy — it tells you the best any policy could possibly do, so you can measure how far LRU or LFU falls short. Chapter 27 studies the online version, where the decision must be made without seeing the rest of the sequence, and measures policies by their competitive ratio against exactly this offline optimum.

Recap

The eight things to carry forward

Where this goes next

Chapter 16 introduces amortized analysis, a different kind of tool: not a way to design algorithms but a way to account for them, showing that an occasional expensive operation is paid for by many cheap ones. It finally proves the dynamic-array doubling claim from Chapter 10, and its techniques are needed for the disjoint-set forests of Chapter 19.


Ch 14 — Dynamic Programming Ch 16 — Amortized Analysis