Data Structure 📂 Tree in Data Structure · 5 of 7 33 min read

Prim's Algorithm: A Hands-On Visual Guide with Python

A hands-on, beginner-to-intermediate tutorial on Prim's algorithm for finding the Minimum Spanning Tree. It pairs an interactive, line-by-line animation (each executing line of Python updates the graph in real time) with a story-driven explanation of the cut property, a fully traced worked example, complexity analysis across three implementations, benefits, six real-world use cases, and a Prim-vs-Kruskal comparison.

Section 01

The Story That Explains Prim's Algorithm

The Fibre-Optic Engineer on a Budget
You have been hired to lay fibre-optic cable connecting six towns. Every possible cable run has a price tag based on distance and terrain. The boss has one rule: connect every town, spend the least money, no wasted loops.

You do not try every combination — there are too many. Instead you start at one town, look at every cable leaving your already-connected region, and lay the single cheapest cable that reaches a town you haven't connected yet. Then you repeat. Your network grows outward like a crystal, one cheapest edge at a time, until every town is on the grid.

That greedy, grow-the-tree-outward strategy is Prim's algorithm.

Prim's algorithm finds the Minimum Spanning Tree (MST) of a connected, weighted, undirected graph: the cheapest set of edges that connects all vertices with no cycles. It was first described by Vojtěch Jarník in 1930, then independently by Robert C. Prim in 1957 and Edsger Dijkstra in 1959 — which is why you'll sometimes see it called the Jarník–Prim algorithm.

🌱
The Core Insight

Maintain one growing tree. At every step, look only at edges that cross from inside the tree to outside it, and greedily grab the lightest one. Because of the cut property, that locally cheapest choice is always part of some globally minimum spanning tree — so greedy is provably optimal here.


Section 02

The Foundation — What Is a Minimum Spanning Tree?

Before the algorithm, you need three definitions. A spanning tree of a graph with V vertices is a set of edges that touches every vertex, stays connected, and contains no cycle — which means it always has exactly V − 1 edges. A graph can have many spanning trees; the minimum spanning tree is simply the one whose edge weights add up to the smallest total.

🌳 The Three Properties Every MST Must Satisfy
Connected
There is a path between every pair of vertices — no town is left stranded.
Acyclic
No loops. A loop means at least one edge is redundant and could be removed to save cost.
Minimal
Among all spanning trees, the sum of edge weights is the lowest possible. Exactly V − 1 edges.
⚠️
MST ≠ Shortest Path

A common trap: the MST minimises the total weight of the whole tree, not the distance between any two specific vertices. The path from A to D inside an MST may be far longer than the shortest A–D path in the original graph. If you need point-to-point shortest paths, you want Dijkstra, not Prim.


Section 03

Why Greedy Works — The Cut Property

Prim's is a greedy algorithm: it never reconsiders a choice. The reason it still lands on the optimal answer is a small piece of graph theory called the cut property.

Splitting the Map in Two
Draw any line that splits your towns into two groups — the ones already connected (inside) and the rest (outside). The cables that cross this line are the only ways to join the two groups. The cut property guarantees that the single cheapest crossing cable is safe to add: it belongs to some minimum spanning tree. Prim's whole job is to apply this rule over and over, each time with the cut between "tree" and "not-yet-tree."
🔑
The Cut Property, Precisely

For any partition of the vertices into two non-empty sets, the minimum-weight edge crossing the partition is contained in at least one MST. Since Prim always adds the lightest edge crossing the cut between visited and unvisited vertices, every edge it picks is provably part of a valid MST. Greedy never has to backtrack.


Section 04

The Algorithm in Five Steps

01
Plant the Tree at a Source Vertex
Pick any starting vertex and mark it as "in the tree." The choice of start does not change the final total cost — only the order edges are discovered.
02
Look at All Crossing Edges
Gather every edge that connects a vertex inside the tree to a vertex outside it. These are the candidates — usually kept in a min-priority-queue so the cheapest is always on top.
03
Pick the Cheapest Crossing Edge
Remove the lightest candidate. If both its endpoints are already in the tree, it would create a cycle — discard it and pick the next one.
04
Absorb the New Vertex
Add the edge to the MST and the newly reached vertex to the tree. Then push that vertex's edges (to still-unvisited neighbours) into the candidate pool.
05
Repeat Until Every Vertex Is In
Loop steps 3–4 until the tree holds all V vertices. You now have V−1 edges and the minimum possible total weight.

Section 05

Hands-On: Watch Every Line of Code Build the Tree

This is the heart of the tutorial. Below, the graph on the left and the Python on the right are wired together. Press Play (or step manually) and watch the highlighted line of code drive exactly one change in the graph: the active line is the task happening on the canvas. Candidate edges glow blue, the edge under consideration pulses amber, accepted MST edges turn solid green, and cycle-forming edges flash red before being discarded.

Interactive — Prim's Algorithm, Line by Line
0
MST Cost
1
In Tree
0
Frontier
candidate considering in MST cycle → skip
Press Play to start, or use Step → to walk through one line at a time.
step 0 / 0

The graph above is the exact one used in the Python code in Section 06 — starting vertex is A. Final MST cost is 16.


Section 06

Python Implementation

Heap-Based Prim — O(E log V)

This is the implementation the visualiser above animates, using Python's built-in heapq min-heap so the cheapest candidate edge is always popped first. Each commented line corresponds to one highlighted step in the animation.

import heapq

def prim(graph, start):
    visited = {start}                      # the tree starts at the source vertex
    mst, total = [], 0                    # chosen edges + running cost
    frontier = []                          # a min-heap of candidate edges

    for nbr, w in graph[start]:            # seed the heap with the source's edges
        heapq.heappush(frontier, (w, start, nbr))

    while frontier:                        # keep going while candidates remain
        w, u, v = heapq.heappop(frontier)  # cheapest edge leaving the tree
        if v in visited:                   # both ends already in? skip the cycle
            continue
        visited.add(v)                     # bring v into the tree
        mst.append((u, v, w))             # lock in the chosen edge
        total += w                         # grow the total cost
        for nbr, w2 in graph[v]:          # expand the frontier from v
            if nbr not in visited:
                heapq.heappush(frontier, (w2, v, nbr))

    return mst, total                     # V-1 edges + minimum cost

Running It on Our Six-Town Graph

graph = {
    'A': [('B', 4), ('F', 6), ('E', 8)],
    'B': [('A', 4), ('F', 2), ('C', 5)],
    'C': [('B', 5), ('F', 3), ('D', 4)],
    'D': [('C', 4), ('F', 7), ('E', 3)],
    'E': [('D', 3), ('F', 5), ('A', 8)],
    'F': [('A', 6), ('B', 2), ('C', 3), ('D', 7), ('E', 5)],
}

edges, cost = prim(graph, 'A')
for u, v, w in edges:
    print(f"{u} -- {v}  (weight {w})")
print("Total MST cost:", cost)
OUTPUT
A -- B (weight 4) B -- F (weight 2) F -- C (weight 3) C -- D (weight 4) D -- E (weight 3) Total MST cost: 16

Dense-Graph Variant — O(V²) with an Adjacency Matrix

When the graph is dense (close to every-pair-connected), a plain array scan beats a heap. No priority queue, just a key[] array holding the cheapest known edge weight to reach each vertex.

import math

def prim_dense(W):                       # W is a V x V weight matrix (inf = no edge)
    n = len(W)
    key    = [math.inf] * n               # cheapest edge reaching each vertex
    parent = [-1] * n                    # which tree-vertex it attaches to
    in_mst = [False] * n
    key[0] = 0                            # start from vertex 0

    for _ in range(n):
        u = min((k, i) for i, k in enumerate(key)
                if not in_mst[i])[1]   # pick closest non-tree vertex
        in_mst[u] = True
        for v in range(n):                 # relax neighbours of u
            if not in_mst[v] and W[u][v] < key[v]:
                key[v], parent[v] = W[u][v], u

    return parent, sum(key)

Section 07

The Run, Traced Step by Step

Here is exactly what the animation does, written out as a table. Starting at A, the heap always serves the cheapest crossing edge. Cycle-forming edges are popped but skipped.

StepEdge PoppedDecisionTree AfterTotal
1A–B (4)Add BA, B4
2B–F (2)Add FA, B, F6
3F–C (3)Add CA, B, F, C9
4C–D (4)Add DA, B, F, C, D13
5D–E (3)Add Eall 6 vertices16
6–10B–C, F–E, A–F, F–D, A–ESkip — both ends already in tree (cycle)unchanged16
🎉
Five Edges, Six Vertices

The MST has exactly V−1 = 5 edges and a total weight of 16. Every other crossing edge was eventually popped and rejected because adding it would have closed a loop. That is the cycle-check (if v in visited) earning its keep.


Section 08

Time & Space Complexity

ImplementationFind-Min StrategyTimeBest For
Adjacency matrixLinear scan of key[]O(V²)Dense graphs (E ≈ V²)
Binary heap + adj. listheapq popO(E log V)Sparse graphs (E « V²)
Fibonacci heapAmortised decrease-keyO(E + V log V)Theoretical optimum; rarely worth the constant factors
📐
Why Density Decides the Implementation

In a dense graph E grows like V², so O(E log V) becomes O(V² log V) — worse than the matrix method's flat O(V²). On sparse graphs the heap wins easily. Space is O(V + E) for the adjacency-list versions. This is exactly the situation where Prim's beats Kruskal's: dense graphs, where you never want to sort all E edges.


Section 09

Benefits of Prim's Algorithm

Provably Optimal
cut property
Greedy, yet the cut property guarantees the exact minimum spanning tree — no approximation, no backtracking, no risk of a sub-optimal answer.
Fast on Dense Graphs
O(V²) matrix form
When almost every pair of vertices is connected, Prim's never sorts all edges. The simple array version runs in O(V²) and ignores edge count entirely.
🌳
Always Connected
single growing tree
At every moment the partial result is one connected tree. Handy when you need a valid, connected sub-network even if you stop early.
💡
Prim vs Kruskal — the One-Liner

Both produce a correct MST. Prim grows one tree from a vertex and shines on dense graphs; Kruskal sorts all edges and merges a forest using union-find, shining on sparse graphs. Pick based on graph density and which data structures you already have.


Section 10

Real-World Use Cases

📡
Network Design
Laying fibre, electrical grids, water pipes, or road networks at minimum total cost while connecting every site. The textbook application.
telecom, utilities, infrastructure
🧮
Cluster Analysis
Build the MST of data points, then cut the heaviest edges. The pieces left behind are natural clusters — the basis of single-linkage clustering.
unsupervised ML, segmentation
🗺️
TSP Approximation
An MST gives a 2-approximation for the metric Travelling Salesman Problem: walk the tree, shortcut repeats. A fast, bounded heuristic for routing.
logistics, route planning
🧩
Maze Generation
Randomly weight a grid graph and run Prim's. The resulting spanning tree is a perfect maze: every cell reachable, exactly one path between any two cells.
games, procedural content
📷
Image Segmentation
Treat pixels as nodes and intensity differences as edge weights. MST-based methods group similar regions, used in computer-vision pipelines.
vision, medical imaging
🔁
Broadcast & Multicast
Distributing one signal to many receivers with the least total link cost maps directly onto building an MST over the network of receivers.
streaming, sensor networks

Section 11

Prim vs Kruskal — Side by Side

PropertyPrim's AlgorithmKruskal's Algorithm
Grows byOne tree, outward from a vertexA forest that merges into one tree
Core data structurePriority queue / key arraySorted edges + union-find (DSU)
Typical timeO(E log V) or O(V²)O(E log E)
Best onDense graphsSparse graphs
Partial result isAlways one connected treeA disconnected forest until the end
Handles disconnected graphNo — needs a connected graphYes — finds a minimum spanning forest

Section 12

Golden Rules

🌳 Prim's Algorithm — Things That Trip People Up
1
Always check for cycles on pop, not on push. A vertex can sit in the heap via several edges. Only when you pop an edge do you test if v in visited — that lazy deletion is what keeps the simple heap version correct.
2
The starting vertex never changes the total cost. Different starts can produce a different order of edges (and occasionally a different MST when weights tie), but the minimum total weight is always identical.
3
Match the implementation to graph density. Sparse graph → binary-heap O(E log V). Dense graph → adjacency-matrix O(V²). Using a heap on a dense graph is a common, silent performance mistake.
4
Prim's requires a connected graph. If the graph might be disconnected and you want a spanning forest, reach for Kruskal's instead, or run Prim's once per connected component.
5
Don't confuse it with Dijkstra. The code looks similar, but Prim's compares edge weight while Dijkstra compares cumulative distance from the source. One builds a cheap tree; the other finds shortest paths.
6
An MST is not unique. When several edges share the same weight, more than one valid minimum spanning tree can exist. They will all share the same total cost.