Graph Theory & Greedy Algorithms

Prim's MST Algorithm Explained

Prim's algorithm grows a minimum spanning tree outward from a single vertex, taking the cheapest edge on the frontier every time. Learn the cut property that proves the greedy choice is always safe, follow a worked six-vertex trace, and see why one line separates it from Dijkstra's algorithm.

12 Min Read Updated: August 2026 Advanced Level
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. Introduction to Prim's Algorithm

Prim's algorithm builds a minimum spanning tree: given a connected, undirected, weighted graph, it selects a subset of edges that touches every vertex, contains no cycle, and has the smallest possible total weight. For a graph with V vertices, that subset always contains exactly V - 1 edges.

The strategy is to grow a single tree outward. Start from any vertex, then repeatedly reach across the boundary of what you have built and pull in the cheapest edge that touches a vertex you do not yet own. Repeat V - 1 times and the tree is finished. There is no backtracking and nothing is ever removed.

That description sounds almost too greedy to be correct. Choosing the cheapest edge available right now, with no lookahead, is exactly the strategy that fails for shortest paths as soon as a negative edge appears. For minimum spanning trees it does not fail, and the reason is a single theorem worth understanding before touching any code.

2. Why It Works: The Cut Property

A cut splits the vertices into two non-empty groups. An edge crosses the cut if its two endpoints land in different groups. The theorem that underwrites Prim's algorithm is this:

The cut property. For any cut of the graph, the minimum-weight edge crossing that cut belongs to some minimum spanning tree. If all edge weights are distinct, that edge belongs to the minimum spanning tree, which is then unique.

The proof is short and worth seeing, because it explains the whole algorithm. Let e be the cheapest edge crossing some cut, and suppose a minimum spanning tree T does not contain it. Adding e to T creates exactly one cycle, and that cycle must cross the cut a second time, using some other edge f. Remove f. You still have a spanning tree, and since e was the cheapest crossing edge, its weight is no greater than that of f, so the new tree is no heavier. A minimum spanning tree therefore exists that contains e.

Now look at what Prim's algorithm does at every step. The vertices already in the tree form one side of a cut, and everything else forms the other. The algorithm selects the cheapest edge crossing precisely that cut. By the cut property, every edge it selects is safe: it belongs to some minimum spanning tree. The greedy choice is never a gamble that happens to pay off, it is a theorem applied V - 1 times.

The example graph with the tree so far being A and C. A dashed line marks the cut. The four edges crossing it are A-B weight 4, B-C weight 2, C-D weight 6 and C-E weight 7. The cheapest crossing edge, B-C at weight 2, is highlighted as the safe choice.
A preview of the six-vertex example graph introduced in section 4. With A and C already in the tree, four edges cross the cut; Prim takes the cheapest, B-C at 2, and the cut property guarantees it is safe.

3. How the Tree Grows

Concretely, the algorithm maintains three things: the set of vertices already in the tree, a key value for every vertex outside it, and a parent pointer recording which tree vertex offered that key.

Each round takes the vertex outside the tree with the smallest key, adds it along with the edge to its parent, and then relaxes: for every neighbour w still outside the tree, if the edge to w is cheaper than key[w], lower key[w] and repoint parent[w].

Note carefully what the key represents. It is the weight of one edge, not the cost of a path. That single detail is what separates this algorithm from Dijkstra's, a point worth returning to once the code is on the page.

A weighted undirected graph with six nodes A to F. The minimum spanning tree is highlighted using edges A-C weight 1, B-C weight 2, D-E weight 2, E-F weight 3 and B-D weight 5, for a total weight of 13. The heavier edges A-B, C-D, C-E and D-F are left unused.
The example graph and its minimum spanning tree: five edges, total weight 13.

4. Step-by-Step Execution

Take the six-vertex graph used throughout our minimum spanning trees guide. Its nine edges are:

A-B 4    A-C 1    B-C 2
B-D 5    C-D 6    C-E 7
D-E 2    D-F 8    E-F 3

Start at A. At each round the "frontier" is the set of edges with exactly one endpoint inside the tree, and the algorithm takes the cheapest of them.

  1. Tree = {A}. Frontier: A-C (1), A-B (4). Cheapest is A-C (1). Add C.
  2. Tree = {A, C}. Frontier: B-C (2), A-B (4), C-D (6), C-E (7). Note that B is now reachable two ways, at 4 through A and at 2 through C, so its key drops to 2. Cheapest is B-C (2). Add B.
  3. Tree = {A, C, B}. Frontier: B-D (5), C-D (6), C-E (7). D is reachable at 5 or 6, so its key is 5. Cheapest is B-D (5). Add D.
  4. Tree = {A, C, B, D}. Frontier: D-E (2), C-E (7), D-F (8). E's key drops from 7 to 2. Cheapest is D-E (2). Add E.
  5. Tree = {A, C, B, D, E}. Frontier: E-F (3), D-F (8). F's key drops from 8 to 3. Cheapest is E-F (3). Add F.

Five edges added for six vertices, and the algorithm stops. The tree is A-C (1), B-C (2), B-D (5), D-E (2), E-F (3), with a total weight of 13. The edges A-B (4), C-D (6), C-E (7) and D-F (8) are never used.

Two moments in that trace are worth dwelling on. In round 3 the algorithm accepted an edge of weight 5 while an edge of weight 2 (D-E) sat elsewhere in the graph untouched. Prim's cannot take D-E yet because neither endpoint is in the tree, and taking it would leave two disconnected fragments rather than one growing tree. In round 4 the key for E fell from 7 to 2 the moment D joined. Keys only ever decrease, and that is what makes the priority queue implementation efficient.

Prim's algorithm on the example graph. Starting from node A the tree grows outward one node at a time, with numbered badges showing the join order A, C, B, D, E, F, each attached by the cheapest edge reaching a new node.
One contiguous tree, grown outward from A. The badges show the join order the trace above produces.

5. Implementation: Lazy and Eager Prim

Two implementations are common, and the difference is what the priority queue holds.

Lazy Prim

The simpler version pushes every edge it encounters into a min-heap and discards entries that turn out to be stale when popped.

function LazyPrim(Graph, start):
    inTree = set()
    pq = empty min-heap keyed by edge weight
    mst = []

    visit(start)                    // mark it and push its edges

    while pq is not empty and size(mst) < V - 1:
        (w, u, v) = pq.pop()        // cheapest edge seen so far
        if v in inTree: continue    // stale: both ends already in the tree
        mst.append((u, v, w))
        visit(v)

    return mst

function visit(x):
    inTree.add(x)
    for each edge (x, y) with weight w:
        if y not in inTree: pq.push((w, x, y))

The if v in inTree: continue line is doing the real work. It is what prevents a cycle, and it is why the heap can safely hold obsolete entries: they are simply skipped when they surface.

Eager Prim

The eager version keeps at most one entry per vertex, the current key[v], and lowers it in place with a decrease-key operation. It needs an indexed priority queue, which is more machinery, but the heap never grows beyond V entries instead of E.

function EagerPrim(Graph, start):
    for each vertex v:
        key[v] = Infinity
        parent[v] = Null
    key[start] = 0
    pq = indexed min-heap of all vertices, keyed by key[]

    while pq is not empty:
        u = pq.popMin()
        inTree.add(u)
        for each edge (u, v) with weight w:
            if v not in inTree and w < key[v]:
                key[v] = w                  // the EDGE weight, not a sum
                parent[v] = u
                pq.decreaseKey(v, w)

    return parent            // parent[] is the tree

Prefer eager Prim on dense graphs, where E is far larger than V and holding every edge in the heap becomes wasteful. Lazy Prim is perfectly reasonable on sparse graphs and is considerably easier to get right.

Side by side comparison of the lazy and eager priority queues at the same moment. Lazy holds four edge entries including two that both reach vertex B. Eager holds one key per vertex: B key 2 via C, D key 6, E key 7 and F infinity.
The same moment, two queues. Lazy stores edges and can hold several entries for one vertex; eager stores one key per vertex.

6. Time and Space Complexity

The practical summary: reach for a binary heap on sparse graphs and the plain O(V2) matrix scan on dense ones. The Fibonacci heap is mostly of theoretical interest.

7. Prim's vs Dijkstra's: One Line Apart

Set the eager Prim pseudocode beside Dijkstra's algorithm and they are nearly the same program. Both keep a key per vertex, both repeatedly extract the minimum, both relax the neighbours of whatever they just extracted. The entire difference is what goes into the key:

Prim:      if w < key[v]:              key[v] = w
Dijkstra:  if key[u] + w < key[v]:     key[v] = key[u] + w

Prim's key is the weight of a single edge. Dijkstra's key is the accumulated length of a whole path from the source. That is why they answer different questions: Prim's asks "what is the cheapest way to attach this vertex to my tree", and Dijkstra's asks "what is the cheapest way to reach this vertex from the source".

It also explains why negative weights break one and not the other. Dijkstra's correctness depends on path costs never decreasing as paths get longer, which a negative edge destroys. Prim's never adds weights together at all, so negative edge weights are completely harmless to it. A minimum spanning tree is well defined on a graph with negative weights, and Prim's finds it without modification.

Comparison of the key values produced by Prim's and Dijkstra's from source A on the same graph. Prim gives A 0, B 2, C 1, D 5, E 2, F 3. Dijkstra gives A 0, B 3, C 1, D 7, E 8, F 11.
One line changed, and four of the six keys differ. Prim stores an edge weight; Dijkstra stores a path total.

8. Prim's vs Kruskal's

Both algorithms are greedy, both are justified by the cut property, and on a graph with distinct weights both return the identical tree. They differ in what they keep connected along the way.

The practical rule follows from density. Kruskal's cost is dominated by sorting, at O(E log E), which is excellent when E is small. Prim's with an adjacency matrix runs in O(V2) regardless of edge count, which wins when the graph is dense. There is also a structural difference on disconnected input: Kruskal's naturally produces a minimum spanning forest, while Prim's from a single start vertex only ever spans that vertex's component, so you must restart it once per component.

9. Practical Notes and Common Pitfalls

Four situations trip people up when they move from the textbook version to real input.

10. Real-World Applications

Minimum spanning trees answer one recurring question: what is the cheapest way to connect everything, with no redundancy? Prim's suits the cases where the network genuinely grows from a source.

Utility and Network Layout

Laying cable, fibre, water pipe or road between a fixed set of sites, where every site must be reachable and total length or cost is what you are minimising, is the original motivation. Prim's paper came out of exactly this problem at Bell Labs.

Cluster Analysis

Building the minimum spanning tree of a point set and then deleting its heaviest edges is single-linkage clustering: removing the k - 1 heaviest edges leaves exactly k clusters. The tree is computed once and every value of k falls out of it.

Approximation for Harder Problems

The minimum spanning tree gives a lower bound on the travelling salesman tour, and doubling its edges yields a tour at most twice optimal on metric instances. It is the starting point for the Christofides construction, which improves that guarantee to 1.5.

Image Segmentation and Maze Generation

Treat pixels as vertices and dissimilarity as edge weight, and MST-based segmentation groups a picture into regions. Run Prim's on a grid with random weights instead and you get a uniform-looking maze, which is why it is a staple of procedural generation.

11. Academic Resources and History

Like several classical graph algorithms, this one was discovered more than once, and the name it carries is not the name of the person who found it first.

For the definitive account of who found what and when, see Graham and Hell's history of the problem. For a rigorous treatment with full proofs of the cut property and both algorithms, the standard reference is Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, in the chapter on minimum spanning trees. Readers interested in how far the complexity has been pushed should look at Fredman and Tarjan's Fibonacci heap result and Chazelle's near-linear algorithm. Full citations appear at the end of this article.

Frequently Asked Questions

Why does Prim's greedy choice always produce a minimum spanning tree?

Because of the cut property: for any split of the vertices into two groups, the cheapest edge crossing that split belongs to some minimum spanning tree. At every step Prim's takes the cheapest edge crossing the cut between the vertices already in its tree and everything else, so each edge it adds is provably safe. The greedy choice is not a lucky heuristic, it is that theorem applied V - 1 times.

What is the difference between Prim's algorithm and Dijkstra's algorithm?

The two are almost the same program, and the entire difference is the key stored per vertex. Prim's uses the weight of a single edge, so it asks how cheaply this vertex can be attached to the tree. Dijkstra's uses the accumulated length of a whole path from the source, so it asks how cheaply this vertex can be reached. That is also why negative weights break Dijkstra's but not Prim's.

Can Prim's algorithm handle negative edge weights?

Yes, without any modification. Prim's never adds edge weights together, it only compares individual edges, so the reasoning that makes Dijkstra's fail on negative input does not apply. A minimum spanning tree is perfectly well defined on a graph with negative weights, and Prim's finds it. The real requirement is that the graph be undirected and connected.

Watch Prim's tree grow outward

The cut property is obvious the moment you see the frontier pick its cheapest edge. Run Prim on a live graph, step by step.

Open the Prim Visualizer

Verified References & Further Reading