Local choices, a global optimum, and the proof that connects them
Lucas P. Cordova, Ph.D.
Willamette University | Fall 2026
When is it safe to commit to the choice that looks best right now?
Take the best-looking choice now, and never revisit it
A rule for choosing, and a proof that the rule never rules out an optimal solution.
What rule would you use to pick the next request?
EIGHT REQUESTS FOR ONE ROOM, LISTED BY FINISH TIME.
A plausible rule is not a correct rule. One counterexample is enough to discard it.
def schedule(requests): # (start, finish) pairs chosen, free_at = [], 0 for s, f in sorted(requests, key=lambda r: r[1]): if s >= free_at: # compatible with all chosen chosen.append((s, f)) free_at = f return chosen
TEAL: CHOSEN. PALE: SKIPPED BECAUSE IT OVERLAPS A CHOSEN REQUEST.
Take any optimal solution. Show you can exchange one of its choices for the greedy choice without making it worse. Then greedy is optimal too.
Greedy is a strategy you must prove, problem by problem. When it fails, the usual repair is dynamic programming, which comes later in the course.
Connect everything, as cheaply as possible
THE RUNNING EXAMPLE FOR THE REST OF THE DECK.
A spanning tree of a connected graph G is a subset of its edges that forms a tree touching every vertex. A minimum spanning tree (MST) is a spanning tree whose total edge weight is as small as possible.
THE MST OF THE EXAMPLE: TOTAL WEIGHT 23.
For any cut, the lightest edge crossing it belongs to the MST (assuming distinct weights).
It tells a greedy algorithm which edges are safe to commit to. Kruskal and Prim differ only in which cuts they look at.
DOTTED LINE: THE CUT. AMBER: ITS LIGHTEST CROSSING EDGE, A–B.
Sort the edges, keep each one that does not close a cycle
Let S be the tree containing u. Any lighter edge leaving S would already have been added and merged into S. So (u, v) is the lightest edge crossing the cut (S, V − S), and the cut property puts it in the MST.
TEAL: IN THE FOREST. PALE: NOT LOOKED AT YET.
MAGENTA DASHED: REJECTED, BOTH ENDPOINTS ALREADY CONNECTED.
Which data structure question did we answer seven times? “Are u and v in the same tree?”
class UnionFind: def __init__(self, items): self.parent = {x: x for x in items} self.rank = {x: 0 for x in items} def find(self, x): # the set's representative if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, a, b): # False if already together ra, rb = self.find(a), self.find(b) if ra == rb: return False if self.rank[ra] < self.rank[rb]: ra, rb = rb, ra self.parent[rb] = ra if self.rank[ra] == self.rank[rb]: self.rank[ra] += 1 return True
find walks to the root. union hangs one root under the other.def kruskal(vertices, edges): # edges: (w, u, v) triples uf = UnionFind(vertices) tree = [] for w, u, v in sorted(edges): # lightest first if uf.union(u, v): # different trees: safe edge tree.append((u, v, w)) return tree
If the edges arrive already sorted (or the weights are small integers you can sort in linear time), what does the running time become?
Grow one tree, always by the cheapest edge leaving it
The tree's vertex set S and everything else form a cut. Prim takes the lightest edge crossing it. That is the cut property, word for word.
“Give me the lightest edge leaving the tree” is a delete-min on a changing collection: a priority queue, built on a binary heap.
TEAL VERTICES: IN THE TREE. AMBER: THE LIGHTEST EDGE LEAVING IT.
import heapq def prim(adj, s): # adj[u]: list of (v, w) in_tree = {s} tree = [] pq = [(w, s, v) for v, w in adj[s]] heapq.heapify(pq) while pq and len(in_tree) < len(adj): w, u, v = heapq.heappop(pq) # lightest candidate if v in in_tree: continue # no longer crosses the cut in_tree.add(v) tree.append((u, v, w)) for x, wx in adj[v]: if x not in in_tree: heapq.heappush(pq, (wx, v, x)) return tree
| Kruskal | Prim | |
|---|---|---|
| Grows | a forest that merges | one connected tree |
| Looks at | edges, globally sorted | edges leaving the tree |
| Data structure | union-find | priority queue (binary heap) |
| Running time | O(E log V) | O(E log V) |
| Natural fit | edge list, sparse graphs, edges already sorted | adjacency lists, dense graphs |
| Correct because | the cut property | the cut property |
One proof, two algorithms. The choice between them is a data structure choice.
Both algorithms, by hand, on one graph
Prim took the heaviest MST edge third and the lightest edge last. Greedy here means lightest edge leaving the tree, not lightest edge overall.
What to keep from today