Prim's Algorithm
Imagine an internet service provider laying fiber-optic cable to connect a hundred cities. Digging trenches is expensive. Each pair of cities has a different cost to connect based on distance and terrain. The provider wants every city linked into one network at the lowest total digging cost. The goal isn't the fastest connection between any two specific cities. It's the cheapest way to bind them all together. This is the minimum spanning tree (MST) problem. Prim's algorithm is one of its two classic solutions. Prim's algorithm builds the MST the way a colony expands. Start from a single city. Then repeatedly annex whichever unconnected neighbor is cheapest to reach from the territory already claimed. This "grow one tree from a seed" strategy contrasts with Kruskal's Algorithm. Kruskal's instead sorts all edges globally and merges disjoint fragments. Prim's approach turns out to be structurally identical to Dijkstra's Algorithm. They share the same priority-queue skeleton and the same relaxation loop. The difference is that Prim's optimizes a different quantity. This makes it an excellent bridge between shortest-path and spanning-tree thinking. The algorithm matters because many problems reduce to finding a minimum spanning tree. These include network design, clustering, image segmentation, and approximation algorithms for harder problems. One example is the traveling salesman problem. When a graph is dense — many edges relative to vertices — Prim's algorithm becomes especially useful. Implemented with the right data structures, it is asymptotically the fastest known approach. Its incremental, tree-growing nature also makes it a natural fit for streaming and incremental-update settings. This article gives a precise statement of the algorithm. It includes a fully worked pseudocode implementation using a priority queue. It also includes a diagram of the frontier as it grows. A rigorous correctness proof is built on the Cut Property. Finally, it offers an honest complexity analysis comparing the binary-heap and Fibonacci-heap variants.
- Prim's algorithm grows a single tree from an arbitrary start vertex, always adding the cheapest edge crossing from the tree to the rest of the graph.
- It is structurally the same loop as Dijkstra's Algorithm: a priority queue keyed by a "best edge so far" value, relaxed as new vertices are visited.
- Correctness follows directly from the Cut Property: at every step, the chosen edge is the unique minimum-weight edge crossing some cut, so it must belong to some MST.
- Running time is O(E log V) with a binary heap, and O(E + V log V) with a Fibonacci heap — the latter matching the theoretical optimum for dense graphs.
- Unlike Kruskal's algorithm, Prim's needs no union-find structure and maintains only one connected component throughout.
- It performs best on dense graphs and in adjacency-matrix form; Kruskal's tends to win on sparse graphs.
Prim's algorithm builds one minimum spanning tree by always crossing its current boundary through the cheapest available edge, a choice the cut property guarantees is always safe.
The idea
A spanning tree of a connected, undirected graph is a subset of edges that connects all vertices without forming a cycle. A minimum spanning tree is a spanning tree whose total edge weight is smallest among all spanning trees. Prim's algorithm computes an MST by maintaining a single growing tree , initially just one arbitrary vertex, and repeatedly adding the lightest edge that connects a vertex already in to a vertex outside . The key structural object is the cut: a partition of into two nonempty sets and . Any edge with one endpoint in each side is said to cross the cut. The cut property states that if an edge is the unique minimum-weight edge crossing some cut, then it belongs to every MST of the graph (or at least one, if ties are broken consistently). Prim's algorithm is essentially a mechanized proof of the cut property: at every iteration, the current tree defines the cut , and the algorithm greedily selects the minimum-weight edge across that exact cut. To find that minimum edge efficiently at every step, the algorithm maintains, for every vertex not yet in the tree, a running value `key[v]` equal to the weight of the cheapest edge seen so far connecting to the tree, along with a pointer `parent[v]` recording which tree vertex offers that edge. A min-priority queue keyed on `key[v]` lets the algorithm repeatedly extract the frontier vertex with the smallest key in logarithmic time. This is precisely the same bookkeeping used in Dijkstra's Algorithm — the difference is that Dijkstra's key is the cumulative distance from the source, while Prim's key is the weight of a single connecting edge, ignoring everything already traveled.
How it works
The algorithm proceeds in three phases: initialization, repeated extraction of the cheapest frontier vertex, and relaxation of its neighbors. 1. Initialize. Pick an arbitrary start vertex . Set `key[r] = 0` and `key[v] = ∞` for every other vertex. Set `parent[v] = NIL` for all . Insert all vertices into a min-priority queue keyed by `key`. 2. Extract. Repeatedly remove the vertex with the smallest `key` value from the queue. This vertex is now considered "in the tree." If `parent[u] ≠ NIL`, the edge `(parent[u], u)` is added to the MST. 3. Relax. For every neighbor of still in the queue, if the weight is smaller than the current `key[v]`, update `key[v] = w(u, v)` and `parent[v] = u`, and adjust 's position in the priority queue (a `decrease-key` operation). The process terminates when the queue is empty, at which point every vertex has been pulled in exactly once and the `parent` pointers describe a spanning tree of minimum total weight. ```text PRIM-MST(G, w, r): for each vertex v in G.V: key[v] ← ∞ parent[v] ← NIL in_tree[v] ← false key[r] ← 0 Q ← min-priority queue containing all vertices, keyed by key[] while Q is not empty: u ← EXTRACT-MIN(Q) # vertex with smallest key not yet in tree in_tree[u] ← true if parent[u] ≠ NIL: add edge (parent[u], u) to MST result for each (u, v) in G.adj[u]: if in_tree[v] = false and w(u, v) < key[v]: key[v] ← w(u, v) parent[v] ← u DECREASE-KEY(Q, v, key[v]) return set of edges {(parent[v], v) : v ≠ r} ``` Two points deserve emphasis. First, `key[v]` never represents a path length or accumulated cost — it is always the weight of a single edge, refreshed whenever a cheaper direct connection to the tree is discovered. Second, the algorithm never needs to check for cycles explicitly (unlike Kruskal's, which needs union-find): because `EXTRACT-MIN` only ever pulls a vertex not already in the tree, every added edge necessarily connects a new vertex, so a cycle can never form.
Complexity & correctness
Correctness. The proof is a direct instantiation of the cut property, proved by induction on the number of edges added. Invariant: After each iteration, the edges chosen so far form a subset of some minimum spanning tree of . Base case: With zero edges chosen, the empty set is trivially a subset of any MST. Inductive step: Suppose the invariant holds after edges have been added, forming tree on vertex set . Consider the cut . The algorithm's next chosen edge , with and , is by construction the minimum-weight edge crossing this cut — this is exactly what `key[]` and `EXTRACT-MIN` guarantee, since every vertex outside has its `key` value set to the weight of the lightest edge connecting it to , and the extracted vertex has the smallest such key among all frontier vertices. By the cut property, this minimum-weight crossing edge belongs to some MST consistent with the edges already chosen. Hence the invariant is preserved. Termination: The loop runs exactly times (once per `EXTRACT-MIN`), adding edges in total (the first extraction adds no edge, since `parent[r] = NIL`). When the queue empties, , and by the invariant the collected edges form a spanning tree that is a subset of an MST on all of — but any spanning tree with edges that is a subset of an MST must equal that MST, since both have the same edge count. Hence the output is a genuine minimum spanning tree. Note that this argument silently assumes is connected; if is disconnected, Prim's algorithm as stated only discovers a minimum spanning tree of the connected component containing , and some `key` values remain forever.
Time complexity. The dominant costs are `EXTRACT-MIN` (called times) and `DECREASE-KEY` (called at most once per edge, so times).
- Binary heap: `EXTRACT-MIN` costs , `DECREASE-KEY` costs . Total:
since the graph is connected, so .
- Fibonacci heap: `EXTRACT-MIN` costs amortized, but `DECREASE-KEY` costs amortized. Total:
This is the asymptotically fastest known comparison-based bound for general graphs and matches Dijkstra's complexity with the same data structure.
- Adjacency matrix without a heap: scanning for the minimum key directly among candidates each round costs overall, which beats when the graph is dense ( close to ), since overhead is avoided entirely.
Space complexity is : the adjacency list (or matrix), the `key`/`parent`/`in_tree` arrays of size , and the priority queue holding up to entries.
Tradeoffs. Prim's algorithm requires no global edge sort and no union-find, but every `DECREASE-KEY` touches the priority queue, so a naive implementation without a proper decrease-key-capable heap (e.g., a plain binary heap with lazy deletion instead) degrades performance. On sparse graphs, Kruskal's Algorithm with a good union-find structure (, dominated by the initial sort) is often simpler to reason about and just as fast in practice. Prim's real advantage emerges on dense graphs and in settings — such as incremental network design — where growing a single connected structure is more natural than merging fragments.
Worked example
Consider six routers with the following candidate cable costs (undirected):
| Edge | Weight |
|---|---|
| A–B | 4 |
| A–C | 1 |
| B–C | 2 |
| B–D | 5 |
| C–D | 8 |
| C–E | 10 |
| D–E | 2 |
| D–F | 6 |
| E–F | 3 |
Start Prim's algorithm at vertex .
| Step | Extracted vertex | Edge added | Weight | key[] updates after this step |
|---|---|---|---|---|
| 1 | A | — | — | B: 4 (via A), C: 1 (via A) |
| 2 | C (key=1, smallest) | A–C | 1 | B: 2 (via C, improved from 4), D: 8 (via C), E: 10 (via C) |
| 3 | B (key=2, smallest) | B–C | 2 | D: 5 (via B, improved from 8) |
| 4 | D (key=5, smallest) | B–D | 5 | E: 2 (via D, improved from 10), F: 6 (via D) |
| 5 | E (key=2, smallest) | D–E | 2 | F: 3 (via E, improved from 6) |
| 6 | F (key=3, smallest) | E–F | 3 | queue empty |
Resulting MST edges: A–C (1), B–C (2), B–D (5), D–E (2), E–F (3). Total weight = 13. Trace check: at Step 2 the algorithm rejects the tempting-looking edge A–B (weight 4) in favor of A–C (weight 1) — the priority queue never even considers A–B until B's key is set, and by the time B is extracted, its key has already been lowered from 4 to 2 via C, correctly reflecting that reaching B through C is cheaper than a direct A–B cable. This illustrates why `key[]` must be continuously relaxed rather than fixed at initialization: the cheapest known connection to the tree can improve as the tree grows, exactly mirroring the relaxation step in Dijkstra's algorithm, but comparing single edges rather than path sums.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Confusing `key[v]` with shortest-path distance | Beginners copy Dijkstra's logic wholesale | `key[v]` is the weight of one edge to the tree, never a cumulative sum |
| Forgetting to skip `parent[r] = NIL` when outputting edges | The start vertex has no incoming tree edge | Guard the "add edge" step with a NIL check |
| Using a plain array scan and calling it | Assuming a heap is present when it isn't | Match your stated complexity to the actual data structure implemented |
| Running on a disconnected graph and expecting a full spanning tree | The cut property only applies within one component | Check connectivity first, or run Prim's per component |
| Re-inserting an already-extracted vertex into the queue during relaxation | Sloppy `in_tree` bookkeeping | Always test `in_tree[v] = false` before relaxing |
| Assuming Prim's finds the unique MST | Ties in edge weight allow multiple valid MSTs of equal total weight | State the result is an MST, not the MST, when weights repeat |
Pioneers & history
Prim's algorithm is named for Robert C. Prim, who published it in 1957 while working at Bell Labs, though the same greedy tree-growing idea had been discovered independently and earlier by the Czech mathematician Vojtěch Jarník in 1930, making the algorithm's true origins a striking case of parallel discovery separated by nearly three decades. Edsger W. Dijkstra rediscovered and popularized essentially the same algorithm in 1959, in the same paper that introduced his shortest-path algorithm — a historical fact that explains why the two algorithms share an almost identical priority-queue skeleton.
Origins · 1 minRobert Clay Prim and how this was discovered
Where this matters
Prim's algorithm underlies network design tools that plan physical infrastructure — fiber-optic backbones, electrical grids, and water distribution networks — where the goal is connecting every site at minimum total cost. In computer vision, MST-based image segmentation algorithms use Prim's-style growth to cluster pixels into regions by treating pixel similarity as edge weight. Clustering algorithms in bioinformatics use minimum spanning trees, often built with Prim's algorithm on dense similarity graphs, to infer evolutionary or structural relationships between genetic sequences. Approximation algorithms for the metric traveling salesman problem construct a 2-approximate tour by first computing an MST — commonly via Prim's algorithm when the underlying graph is dense or given as a distance matrix — and then traversing it. Network routing protocols and circuit design tools for VLSI layout also invoke MST computation as a subroutine, frequently choosing Prim's algorithm specifically because chip layouts tend to produce dense, matrix-representable graphs where its variant is fastest in practice.
:::quiz[What does key[v] represent during execution of Prim's algorithm?]
- The shortest path distance from the root to v
- The total number of edges explored so far
- The weight of the lightest edge connecting v to the current tree ✓
- The degree of vertex v in the final MST
Ask the tutor: How would you modify Prim's algorithm to efficiently support edge weight updates without recomputing the MST from scratch?
Is Prim's algorithm faster than Kruskal's algorithm?
It depends on graph density and implementation. With a binary heap, Prim's runs in , essentially the same as Kruskal's (since for simple graphs). On dense graphs, Prim's with a plain adjacency matrix achieves , beating Kruskal's sort-dominated bound. With a Fibonacci heap, Prim's reaches , the best known bound for general graphs. Kruskal's tends to be simpler to implement correctly on sparse graphs, since it avoids decrease-key bookkeeping entirely. In practice, benchmark both on your specific graph shape before choosing.
Can Prim's algorithm handle negative edge weights?
Yes, unlike some shortest-path algorithms, Prim's algorithm works correctly with negative edge weights, because it never reasons about cumulative path sums — only about the single cheapest edge crossing the current cut at each step. The cut property makes no assumption about weight positivity; it only requires a strict, well-defined minimum among crossing edges. This is a key structural difference from Dijkstra's algorithm, which fails with negative weights precisely because it accumulates path costs that a later negative edge could invalidate.
Does Prim's algorithm produce a unique minimum spanning tree?
Not necessarily. If the graph has distinct edge weights, the MST is provably unique, and Prim's algorithm will find it regardless of starting vertex. If edge weights tie, multiple spanning trees can share the same minimum total weight, and different tie-breaking rules in `EXTRACT-MIN` or different start vertices can yield different — but equally minimal — trees. The algorithm always produces a correct MST, just not always the same one across runs when ties exist.
Why does starting vertex choice not affect the total MST weight?
Because the cut property is a statement about edges and cuts, not about any particular starting point. No matter which vertex begins the tree, every step still selects the minimum-weight edge crossing the current cut, and the induction proof of correctness never references which vertex was chosen first. The specific sequence of vertices visited may differ, and with tied weights the exact tree may differ, but the total weight of any MST produced is always the graph's true minimum.
How does Prim's algorithm compare structurally to Dijkstra's algorithm?
They share an almost identical skeleton: both maintain a priority queue of vertices keyed by a numeric value, both repeatedly extract the minimum, and both relax neighboring edges upon extraction. The difference is entirely in what the key represents. Dijkstra's `dist[v]` is the shortest cumulative path weight from the source, requiring `dist[u] + w(u,v)` in relaxation. Prim's `key[v]` is the weight of a single edge to the tree, requiring just `w(u,v)` in relaxation, with no accumulation. This distinction is exactly why Dijkstra's fails on negative edges while Prim's does not.
What happens if two edges crossing a cut have equal minimum weight?
The cut property still guarantees that at least one MST contains one of the tied minimum edges, but not that both must be included. Prim's algorithm will deterministically pick whichever tied edge its priority queue happens to extract first (often decided by insertion order or an internal tie-breaking rule), and the resulting tree is still a valid MST. Different valid MSTs may result from different tie-breaking choices, but all share the same total weight, since they are all genuinely minimal.
Is Prim's algorithm used for directed graphs?
No. Minimum spanning trees are defined only for undirected, connected graphs, since the concept relies on edges being usable in either direction to reach every vertex. The directed analogue — finding a minimum-weight arborescence rooted at a specific vertex — is a different problem solved by the Chu–Liu/Edmonds' algorithm, which does not reduce to a simple priority-queue frontier growth like Prim's does.