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.
How To Use It
For most production cases, use the binary-heap version. Implement it with lazy deletion (push updated pairs, skip stale pops) to avoid costly decrease-key:
heap := [(0, r)]
in_tree[v] := false; key[v] := +inf; key[r] := 0
while heap not empty:
(w, u) := heap.extract-min()
if in_tree[u]: continue
in_tree[u] := true
add edge via parent[u] if u != r
for each (u, v, ww) in adj[u]:
if not in_tree[v] and ww < key[v]:
key[v] := ww
parent[v] := u
heap.push((ww, v))
Use the array-based variant O(|V|^2) if the graph is so dense that |E| = Theta(|V|^2).
Integration tips:
- remember to initialize
key[r] = 0exactly once; all others+infinity - track
in_tree[]explicitly so the lazy pop check isO(1) - if you need the MST edges in weight-order at the end, sort the edges of the final tree; do not try to extract in-order during the run
Transfer / Where This Shows Up Later
- S3 (systems). Building cluster-wide spanning trees for monitoring or log shipping uses Prim with latency weights.
- S4 (compilers). Tree-shape extraction from CFGs and call graphs leans on Prim-like scaffolding.
- S5 (OS). Multicast or broadcast tree construction in kernel-bypass NICs.
- S6 (databases). Replication topology with cost-aware spanning trees for write fan-out.
- S7 (DDD). When minimizing cross-context coupling, Prim over a weighted context-map picks the cheapest backbone.
- S8 (distributed). Gossip-overlay and multicast trees, spanning-tree protocols in Ethernet (STP), and similar.
Back-link to concept 10 (Dijkstra) for the priority-queue scaffold comparison, and to concept 13 for the cut-property correctness proof.
Check Yourself
- Explicitly compare Prim and Dijkstra: what is the same, what is different?
- Why does the cut property justify Prim: which cut is being processed at each extract-min?
- For a dense graph with
|V| = 1000, estimate the runtime of Prim with a binary heap vs an array. - What does
parent[]represent at the end of Prim, and how do you reconstruct the MST edges from it? - Give one graph where Prim and Kruskal produce different edge sets with equal total weight, and one where they produce the same set.
- Why does choosing a different root not change the total weight of the MST?
Mini Drill or Application
Run Prim from vertex a on
a - b (7), a - d (5)
b - c (8), b - d (9), b - e (7)
c - e (5)
d - e (15), d - f (6)
e - f (8), e - g (9)
f - g (11)
Record the extraction order and the final MST weight.
Implementation drill.
- Implement binary-heap Prim with lazy deletion.
- Implement array-based
O(|V|^2)Prim. - Run both on a complete graph of
200vertices with random[1, 100]weights; verify total weight matches your Kruskal implementation on the same graph. - Benchmark all three (heap Prim, array Prim, Kruskal) on sparse, medium, and dense graphs; produce a short table of crossover densities.
- Use Prim to compute an MST of your university's campus as a weighted graph (distances between buildings); produce a minimal-cable layout.
Read This Only If Stuck
- CLRS 21.2: Kruskal and Prim
- CLRS 21.2: Kruskal and Prim (Part 3)
- CLRS 21.2: Kruskal and Prim (Part 4)
- CLRS 21.2: Kruskal and Prim (Part 5)
- ADM 6.1: Minimum Spanning Trees
- Sedgewick: Elementary Graph Algorithms (Part 3)
- Competitive Programming 4.3.3: Prim's Algorithm
- Competitive Programming 4.3: Minimum Spanning Tree
- cp-algorithms: MST - Prim
- Sedgewick & Wayne, algs4 booksite: MSTs