Prim with Priority Queue
What This Concept Is
Prim's algorithm grows one tree from an arbitrary root. At each step, it adds the minimum-weight edge that leaves the tree and goes into an out-of-tree vertex. A priority queue keyed on "minimum edge weight from the current tree to each out-of-tree vertex" does the bookkeeping:
for each v in V: key[v] := +infinity; parent[v] := null
key[r] := 0 # r is the root
Q := priority queue of all V, keyed by key
while Q is not empty:
u := Q.extract-min()
for each edge (u, v, w) in adj[u]:
if v in Q and w < key[v]:
key[v] := w
parent[v] := u
Q.decrease-key(v, w)
Each extract-min pulls one vertex into the tree. The edges (parent[v], v) for each v != r form the MST.
Total time:
- Binary heap.
O((|V| + |E|) log |V|). - Fibonacci heap.
O(|E| + |V| log |V|). - Array.
O(|V|^2), best on dense graphs.
Why It Matters Here
Prim is structurally the same shape as Dijkstra - grow a tree from a root, maintain a "frontier" priority queue, extract min, relax. The difference is only what key[v] stores:
- Dijkstra:
key[v]= best known distance fromstov. - Prim:
key[v]= minimum edge weight from the current tree tov.
Recognizing this similarity is not cosmetic - it tells you the same data structures and engineering tradeoffs apply, and it makes memorizing neither necessary.
Prim vs Kruskal:
- Prim. Grows one tree; priority queue over vertices; best when graph is dense or adjacency lists are already in memory. Natural when you want the MST to grow incrementally from a known root.
- Kruskal. Grows a forest; union-find over edges; best when graph is sparse or edges are already sorted. Produces MST edges in weight order as a by-product.
Both compute an MST in O(|E| log |V|) for the standard implementations; the choice is about the input format and downstream bookkeeping, not asymptotic cost.
Concrete Example
Graph:
Start at a. Initial key = [a:0, b:inf, c:inf, d:inf, e:inf].
| step | extract (key) | updates after | parent updates | in-tree |
|---|---|---|---|---|
| 1 | a (0) | b:1, c:5 | parent[b]=a, parent[c]=a | {a} |
| 2 | b (1) | c:2, d:3 (beats 5/inf) | parent[c]=b, parent[d]=b | {a,b} |
| 3 | c (2) | d: no (4>3), e:6 | parent[e]=c | {a,b,c} |
| 4 | d (3) | e: no (7>6) | unchanged | {a,b,c,d} |
| 5 | e (6) | - | - | {a,b,c,d,e} |
MST edges: (a,b), (b,c), (b,d), (c,e). Total weight 1 + 2 + 3 + 6 = 12 - same as Kruskal, as expected.
Common Confusion / Misconceptions
- "Prim and Dijkstra are the same algorithm." Same shape, different objective. Dijkstra relaxes
dist[u] + w; Prim relaxes with raw edge weightw. Swapping them silently gives you a shortest-path tree instead of an MST (or vice versa). - "Prim builds an MST rooted at the start vertex." The MST is unique (for distinct weights) and does not depend on the root. Different roots produce the same edge set; only the traversal order differs.
- "
decrease-keyis cheap in a binary heap." It requires an index from vertex-id to heap-position, or lazy deletion. The idiomatic lazy-deletion variant pushes duplicates and discards stale pops. - "Prim cannot produce a minimum spanning forest." It cannot directly; it grows one tree and terminates when that tree spans one component. For forests, run Prim from each unvisited vertex - or just use Kruskal.
- "Dense graphs need Fibonacci heaps." On truly dense graphs (
|E| = Theta(|V|^2)), theO(|V|^2)array-based Prim beats both binary heap and Fibonacci heap.