The Story — Connecting Villages With Fibre Cable
The rule is simple: every village must be connected to every other one, directly or through other villages. But the company wants to spend the least total money.
Will they buy a cable that makes a loop? Never. A loop means one cable is extra. They can remove it and everyone still stays connected.
The cheapest set of cables with no loops is called a Minimum Spanning Tree (MST).
A spanning tree of a connected graph uses all the nodes and just enough edges to connect them, with no cycles. If there are V nodes, a spanning tree always has exactly V − 1 edges. The minimum spanning tree is the one with the smallest total edge weight.
The Three Algorithms at a Glance
On the same graph, all three give an MST with the same total weight. They just add the edges in a different order. Prim grows one tree. Kruskal grows a forest of small trees that slowly join. Borůvka joins many trees at once in each round.
Watch Them Build — Prim vs Kruskal
Same graph, same answer (total = 39). Watch how they build it. Prim's green tree grows from A. Kruskal's colours show separate groups that merge. A red dotted edge was rejected because it would make a cycle.
Prim's — one tree grows
Kruskal's — cheapest edge first
Prim order: A–D, D–F, A–B, B–E, E–C, E–G. Kruskal order: A–D, C–E, D–F, A–B, B–E, then rejects B–C, E–F, B–D, then takes E–G.
The MST Simulator — Build Your Own Graph
Draw nodes and weighted edges. Pick Prim's, Kruskal's or Borůvka's. Each click on Step runs one line. Watch the priority queue, the sorted edge list, or the component list change. The Union-Find table shows each node's parent. You can also build your own tree and check it against the real MST.
1. Classic graph: type your guess for the total weight, then check it. Run all three algorithms. The total is always 39.
2. Run Prim from different start nodes. The edge order changes, but the total does not.
3. In Kruskal, turn Path compression off and on. Watch the parent column. With it on, nodes point straight at the root.
4. Load Equal Weights, pick your own tree in Pick Edges mode, then check it. Many different trees are all correct.
5. Load Disconnected. There is no spanning tree, so Kruskal and Borůvka build a spanning forest. Prim only covers the start's part.
Union-Find Playground — The Engine Inside Kruskal
Kruskal asks one question again and again: "Are u and v already in the same group?" A Union-Find (also called Disjoint Set) answers it almost instantly. Each node points to a parent. Follow the parents up to the root. Two nodes with the same root are in the same set.
parent[x] until a node points to itself. That node is the set's leader (root).find(x), point every node on the path straight to the root. Later finds become almost O(1).Turn both options off. Press Make a Long Chain. You get a tall chain, so find(0) must walk every node. Now turn Path compression on and run find(0). Watch the whole chain flatten in one step.
Step-by-Step Traces on the Classic Graph
Edges: 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.
Prim's (start at A)
| Step | Tree So Far | Cheapest Edge Leaving the Tree | Action | Total |
|---|---|---|---|---|
| 1 | {A} | A–D (5) | Add D | 5 |
| 2 | {A, D} | D–F (6) | Add F | 11 |
| 3 | {A, D, F} | A–B (7) | Add B | 18 |
| 4 | {A, B, D, F} | B–E (7) | Add E | 25 |
| 5 | {A, B, D, E, F} | E–C (5) | Add C | 30 |
| 6 | {A, B, C, D, E, F} | B–C (8), E–F (8), B–D (9) are stale | Skip stale edges | 30 |
| 7 | {A, B, C, D, E, F} | E–G (9) | Add G — done | 39 |
Kruskal's (edges sorted by weight)
| Edge | Weight | Same Set? | Action | Sets After |
|---|---|---|---|---|
| A–D | 5 | No | Take | {A,D} {B} {C} {E} {F} {G} |
| C–E | 5 | No | Take | {A,D} {B} {C,E} {F} {G} |
| D–F | 6 | No | Take | {A,D,F} {B} {C,E} {G} |
| A–B | 7 | No | Take | {A,B,D,F} {C,E} {G} |
| B–E | 7 | No | Take | {A,B,C,D,E,F} {G} |
| B–C | 8 | Yes | Reject — cycle | no change |
| E–F | 8 | Yes | Reject — cycle | no change |
| B–D | 9 | Yes | Reject — cycle | no change |
| E–G | 9 | No | Take — 6 edges, done | {A,B,C,D,E,F,G} |
Borůvka's
| Round | Cheapest Edge for Each Component | Edges Added | Components After |
|---|---|---|---|
| 1 | A→A–D 5 · B→A–B 7 · C→C–E 5 · D→A–D 5 · E→C–E 5 · F→D–F 6 · G→E–G 9 | A–D, A–B, C–E, D–F, E–G | {A,B,D,F} {C,E,G} |
| 2 | {A,B,D,F}→B–E 7 · {C,E,G}→B–E 7 | B–E | {A,B,C,D,E,F,G} |
In round 1, node B has two edges of weight 7 (A–B and B–E). If two groups break ties in different ways, they can pick edges that form a cycle. The fix is simple. Compare by (weight, edge number), so every group follows the same order.
Python Implementation
# Shared graph for all three examples
edges = [('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)]
nodes = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
graph = {n: [] for n in nodes} # adjacency list
for u, v, w in edges:
graph[u].append((v, w))
graph[v].append((u, w))
1. Prim's Algorithm (heapq)
import heapq
def prim(graph, start):
in_tree = {start}
pq = [(w, start, v) for v, w in graph[start]]
heapq.heapify(pq)
mst, total = [], 0
while pq and len(in_tree) < len(graph):
w, u, v = heapq.heappop(pq) # cheapest edge
if v in in_tree:
continue # stale: would make a cycle
in_tree.add(v)
mst.append((u, v, w))
total += w
for x, wx in graph[v]: # new edges leaving the tree
if x not in in_tree:
heapq.heappush(pq, (wx, v, x))
return mst, total
mst, total = prim(graph, 'A')
print("MST edges:", mst)
print("Total weight:", total)
2. Union-Find + Kruskal's Algorithm
class DSU:
def __init__(self, items):
self.parent = {x: x for x in items} # each node is its own set
self.rank = {x: 0 for x in items}
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # path compression
return self.parent[x]
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False # same set: cycle!
if self.rank[ra] < self.rank[rb]: # union by rank
ra, rb = rb, ra
self.parent[rb] = ra
if self.rank[ra] == self.rank[rb]:
self.rank[ra] += 1
return True
def kruskal(nodes, edges):
dsu = DSU(nodes)
mst, total = [], 0
for u, v, w in sorted(edges, key=lambda e: e[2]):
if dsu.union(u, v):
mst.append((u, v, w))
total += w
print(f" take {u}-{v} ({w})")
else:
print(f" reject {u}-{v} ({w}) -> would make a cycle")
if len(mst) == len(nodes) - 1:
break # V - 1 edges: done
return mst, total
mst, total = kruskal(nodes, edges)
print("Total weight:", total)
3. Borůvka's Algorithm
def boruvka(nodes, edges):
dsu = DSU(nodes)
mst, total, rnd = [], 0, 0
comps = len(nodes)
while comps > 1:
rnd += 1
cheapest = {} # root -> best edge
for i, (u, v, w) in enumerate(edges):
ru, rv = dsu.find(u), dsu.find(v)
if ru == rv:
continue # inside one component
for r in (ru, rv):
# tie-break by (weight, index) to avoid cycles
if r not in cheapest or (w, i) < (cheapest[r][2], cheapest[r][3]):
cheapest[r] = (u, v, w, i)
if not cheapest:
break # graph is not connected
added = []
for u, v, w, i in cheapest.values():
if dsu.union(u, v): # may be chosen twice
mst.append((u, v, w))
total += w
comps -= 1
added.append(f"{u}-{v}({w})")
print(f" Round {rnd}: added {added}, components left = {comps}")
return mst, total
mst, total = boruvka(nodes, edges)
print("Total weight:", total)
Prim vs Kruskal vs Borůvka
V = number of nodes. E = number of edges.
| Property | Prim's | Kruskal's | Borůvka's |
|---|---|---|---|
| Main idea | Grow one tree | Cheapest edge overall, skip cycles | Every component picks its cheapest edge |
| Key data structure | Priority queue (min-heap) | Sorted list + Union-Find | Union-Find + "cheapest" array |
| Time | O(E log V) with heap | O(E log E) (sorting) | O(E log V) |
| Best for | Dense graphs, adjacency list/matrix | Sparse graphs, edge list input | Parallel / distributed computing |
| Needs a start node? | Yes | No | No |
| Disconnected graph | Spans only the start's part | Gives a spanning forest | Gives a spanning forest |
| Easy to run in parallel? | No | Sorting only | Yes — each component works alone |
In each round, every component joins with at least one other component. So the number of components at least halves:
V → V/2 → V/4 → … → 1. That takes at most log₂ V rounds. With 1,000,000 nodes, that is only about 20 rounds.
Where MSTs Are Used
Golden Rules
find(u) == find(v), the edge makes a cycle, so reject it.