Kruskal, Prim algorithms. Connecting all nodes with minimum edge weight.
Self-serve tutorial - low prerequisites, straightforward concepts.
You’re wiring up a campus network. Every building must be connected, cable is expensive, and you’re allowed to choose which paths to dig. How do you guarantee you connect everything with the least total cable length—without trying every possible network layout?
A minimum spanning tree (MST) is a set of edges that connects all vertices with no cycles and minimum total weight. Two core greedy facts make MSTs tractable: the cut property (the lightest edge across a cut is safe) and the cycle property (the heaviest edge on a cycle is never needed). Kruskal grows a forest by adding the next-lightest safe edge (using DSU/Union-Find). Prim grows one tree by repeatedly adding the lightest edge leaving the current tree (using a priority queue).
You’re given a connected, undirected, weighted graph , where each edge has a weight (cost, length, time, etc.). You want to pick some edges so that:
A spanning tree is exactly a connected, acyclic subgraph that includes all vertices. So an MST is the best (minimum-weight) spanning tree.
A spanning tree on vertices always has exactly edges. This is a powerful constraint:
So an MST is “choose edges carefully.”
Suppose you have 4 vertices and weighted edges:
One spanning tree is with weight .
Another spanning tree is with weight .
The MST would choose the first: total 4.
Most graph optimization problems don’t allow “just keep taking the cheapest available step.” MST is special: it has local rules (cut/cycle properties) that certify a greedy choice won’t block optimality.
That’s the real theme of MST: we need a proof tool that justifies greedy growth.
If we’re building a spanning tree and we add an edge too early, we might later regret it. So we want a condition that says:
“No matter what the optimal MST looks like, there exists an MST that contains this edge.”
Such an edge is called safe.
A cut is a partition of vertices into two non-empty sets:
An edge crosses the cut if it has one endpoint in and the other in .
Intuition: if your current partial structure has connected everything in and you want to connect to the outside, you must pick some edge that crosses the cut.
Let be any cut. Consider all edges that cross the cut. Let be a lightest edge among them (minimum weight). Then:
Cut property: The edge is safe: there exists an MST that contains .
If the lightest crossing edge is unique, then it belongs to every MST.
Below, is the left group and is the right group. The cut-crossing edges are those that go between the groups.
<svg width="640" height="220" viewBox="0 0 640 220" xmlns="http://www.w3.org/2000/svg">
<style>
.v { fill:#fff; stroke:#111; stroke-width:2; }
.lbl { font: 14px sans-serif; fill:#111; }
.cut { stroke:#888; stroke-width:2; stroke-dasharray:6 6; }
.e { stroke:#999; stroke-width:2; }
.elight { stroke:#0b6; stroke-width:5; }
.w { font: 13px sans-serif; fill:#333; }
.region { fill:#f4f8ff; stroke:#c7d6ff; stroke-width:2; }
</style>
<!-- regions -->
<rect x="20" y="20" width="270" height="180" rx="14" class="region"/>
<rect x="350" y="20" width="270" height="180" rx="14" class="region"/>
<text x="35" y="45" class="lbl">S</text>
<text x="365" y="45" class="lbl">V \ S</text>
<!-- cut line -->
<line x1="320" y1="10" x2="320" y2="210" class="cut"/>
<!-- vertices left -->
<circle cx="90" cy="80" r="16" class="v"/><text x="85" y="85" class="lbl">A</text>
<circle cx="200" cy="140" r="16" class="v"/><text x="195" y="145" class="lbl">B</text>
<!-- vertices right -->
<circle cx="430" cy="80" r="16" class="v"/><text x="425" y="85" class="lbl">C</text>
<circle cx="540" cy="140" r="16" class="v"/><text x="535" y="145" class="lbl">D</text>
<!-- crossing edges -->
<line x1="90" y1="80" x2="430" y2="80" class="e"/>
<text x="255" y="70" class="w">w=5</text>
<line x1="200" y1="140" x2="540" y2="140" class="e"/>
<text x="360" y="132" class="w">w=4</text>
<line x1="200" y1="140" x2="430" y2="80" class="elight"/>
<text x="305" y="110" class="w" fill="#0b6">w=2 (lightest)</text>
</svg>The cut property says: the green edge of weight 2 (the lightest crossing edge) can be included in some MST safely.
We’ll use an “exchange argument.”
The key move is: swap in a light edge and swap out a heavier-or-equal one without breaking spanning-tree structure.
In both cases, they repeatedly choose a lightest edge crossing a relevant cut.
The cut property tells us which edges are safe to add.
The cycle property tells us which edges are safe to discard.
This becomes especially helpful for understanding Kruskal (where we sort edges and try them in order) and for reasoning about correctness.
Consider any cycle in the graph. Let be an edge on that cycle with maximum weight (a heaviest edge).
Cycle property: There exists an MST that does not contain . In particular, if is uniquely the heaviest on the cycle, it is in no MST.
Intuition: within a cycle, you already have a redundant route. If one edge is the most expensive, it’s the best candidate to remove.
Here’s a 4-cycle. The heaviest edge (weight 9) is highlighted in red; the cycle property says it cannot appear in an MST if it’s uniquely heaviest.
<svg width="640" height="240" viewBox="0 0 640 240" xmlns="http://www.w3.org/2000/svg">
<style>
.v { fill:#fff; stroke:#111; stroke-width:2; }
.lbl { font: 14px sans-serif; fill:#111; }
.e { stroke:#777; stroke-width:3; }
.heavy { stroke:#c00; stroke-width:6; }
.w { font: 13px sans-serif; fill:#333; }
</style>
<!-- vertices -->
<circle cx="180" cy="70" r="16" class="v"/><text x="175" y="75" class="lbl">A</text>
<circle cx="460" cy="70" r="16" class="v"/><text x="455" y="75" class="lbl">B</text>
<circle cx="460" cy="170" r="16" class="v"/><text x="455" y="175" class="lbl">C</text>
<circle cx="180" cy="170" r="16" class="v"/><text x="175" y="175" class="lbl">D</text>
<!-- edges -->
<line x1="180" y1="70" x2="460" y2="70" class="e"/>
<text x="310" y="58" class="w">w=3</text>
<line x1="460" y1="70" x2="460" y2="170" class="e"/>
<text x="472" y="125" class="w">w=4</text>
<line x1="460" y1="170" x2="180" y2="170" class="e"/>
<text x="310" y="190" class="w">w=2</text>
<line x1="180" y1="170" x2="180" y2="70" class="heavy"/>
<text x="110" y="125" class="w" fill="#c00">w=9 (heaviest)</text>
</svg>Let be an MST. Suppose (for contradiction, or to build an alternative MST) that contains .
So we can always replace the heavy cycle edge with a no-heavier alternative.
Kruskal adds edges in increasing weight order, skipping any edge that would make a cycle.
When an edge would complete a cycle, it’s necessarily not needed; and if it’s among the heavier edges on that cycle, skipping it is consistent with the cycle property.
Both are two sides of the same “exchange” idea: MSTs are flexible enough that local replacements preserve optimality.
An MST has edges. A natural greedy plan is:
But there are two distinct ways to organize this growth:
Both are correct because each step corresponds to choosing a lightest edge across some cut.
Sort all edges by weight. Scan from lightest to heaviest; add an edge if it connects two different components (i.e., doesn’t form a cycle).
We need to quickly answer:
With path compression + union by rank/size, each operation is effectively constant amortized time.
At any moment, Kruskal’s chosen edges form a forest. The components define a cut: pick any component as , and consider edges leaving it. The next chosen edge is the lightest edge that connects two components (a lightest edge across some cut), so by the cut property it’s safe.
So overall: .
Start from any vertex. Maintain a set of vertices already in the tree. Repeatedly add the lightest edge that goes from to .
This is “like Dijkstra,” but the key is: Prim uses edge weights directly to cross the boundary, not accumulated path distances.
Typical implementation tracks for each vertex the cheapest edge connecting it to (a key), and updates keys as grows.
At each step, the algorithm considers the cut and picks the lightest edge leaving (via the minimum key). By the cut property, that edge is safe.
| Feature | Kruskal | Prim | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Growth style | forest → merges components | one tree expands | ||||||||
| Key greedy choice | next lightest edge that doesn’t cycle | lightest edge crossing | ||||||||
| Main structure | DSU (Union-Find) | min-priority queue | ||||||||
| Typical time | $O( | E | \log | E | )$ | $O( | E | \log | V | )$ |
| Works on disconnected graph? | gives minimum spanning forest | needs a start per component |
If is not connected, an MST doesn’t exist (can’t span all vertices). But both ideas extend:
Classical MST is defined for undirected graphs. If edges are directed, the analogous structure is a minimum spanning arborescence (Edmonds’ algorithm), which is a different topic.
MSTs are a core pattern: global optimization made greedy through structural properties (cuts/cycles). Once you internalize that, you’ll recognize similar proof techniques elsewhere.
MST gives a baseline “no redundancy” network. Real systems add redundancy later, but MST is often the starting scaffold.
Given points and pairwise distances, compute the MST of the complete graph. If you delete the largest edges in the MST, you get connected components—this is exactly single-linkage hierarchical clustering.
MSTs appear inside larger algorithms:
Even when you don’t care about total weight, MST has a useful minimax flavor:
This isn’t the same objective as MST, but MST ends up optimal for it too.
These properties are classic examples of:
If you later study matroids, MST becomes a flagship example: the set of acyclic edge sets forms a matroid (graphic matroid), and greedy works.
When you see an MST problem, ask:
That last question is the “transferable skill”: reasoning locally about global optimality.
Graph with vertices V = {A, B, C, D, E}. Weighted edges:
(A,B)=1, (C,D)=1, (B,C)=2, (A,C)=3, (B,D)=4, (C,E)=5, (D,E)=6.
Find an MST using Kruskal.
Sort edges by weight:
1: (A,B), (C,D)
2: (B,C)
3: (A,C)
4: (B,D)
5: (C,E)
6: (D,E)
Initialize DSU components:
{A}, {B}, {C}, {D}, {E}
MST = ∅
Consider (A,B) with w=1:
Find(A) ≠ Find(B) ⇒ add it.
Union(A,B)
Components: {A,B}, {C}, {D}, {E}
MST edges: {(A,B)}
Consider (C,D) with w=1:
Find(C) ≠ Find(D) ⇒ add it.
Union(C,D)
Components: {A,B}, {C,D}, {E}
MST edges: {(A,B), (C,D)}
Consider (B,C) with w=2:
Find(B) is component {A,B}; Find(C) is component {C,D} ⇒ different ⇒ add it.
Union({A,B}, {C,D})
Components: {A,B,C,D}, {E}
MST edges: {(A,B), (C,D), (B,C)}
Consider (A,C) with w=3:
Now Find(A) = Find(C) (both in {A,B,C,D}) ⇒ adding would create a cycle ⇒ skip.
MST unchanged.
Consider (B,D) with w=4:
Find(B) = Find(D) (same component) ⇒ would create a cycle ⇒ skip.
Consider (C,E) with w=5:
Find(C) ≠ Find(E) ⇒ add it.
Union({A,B,C,D}, {E})
Components: {A,B,C,D,E}
MST edges: {(A,B), (C,D), (B,C), (C,E)}
Stop condition:
We have |V|-1 = 4 edges ⇒ done.
Total MST weight = 1 + 1 + 2 + 5 = 9.
Insight: Kruskal’s logic is: “take the cheapest edge that connects two different components.” The DSU is what makes “different components?” fast. Every accepted edge is safe via the cut property on the cut induced by components.
Use the same graph as above:
(A,B)=1, (C,D)=1, (B,C)=2, (A,C)=3, (B,D)=4, (C,E)=5, (D,E)=6.
Run Prim starting at A and produce the MST edges.
Initialize:
S = ∅
key[A]=0; key[B]=key[C]=key[D]=key[E]=+∞
parent[·]=nil
Extract min key: A (key 0). Add A to S.
Relax neighbors of A:
Extract min key among not-in-S: B (key 1). Add B to S.
Relax neighbors of B:
Extract min key: C (key 2). Add C to S.
Relax neighbors of C:
Extract min key: D (key 1). Add D to S.
Relax neighbors of D:
Extract min key: E (key 5). Add E to S.
Done (all vertices included).
MST edges come from parents (excluding the start A):
(B, parent[B]=A) ⇒ (A,B)
(C, parent[C]=B) ⇒ (B,C)
(D, parent[D]=C) ⇒ (C,D)
(E, parent[E]=C) ⇒ (C,E)
Total weight = 1 + 2 + 1 + 5 = 9
Insight: Prim makes the cut explicit: at each step, S is the current tree’s vertex set. The chosen edge is the minimum-weight edge crossing (S, V\S), which is safe by the cut property—so the tree can grow greedily.
Graph contains a cycle A—B—C—D—A with weights:
(A,B)=2, (B,C)=3, (C,D)=4, (D,A)=10.
There may be other edges elsewhere, but focus on this cycle. Can (D,A) with weight 10 be in any MST?
Identify the cycle C = (A,B),(B,C),(C,D),(D,A).
Find the heaviest edge on the cycle:
w(D,A)=10 is larger than 2,3,4 ⇒ it is uniquely heaviest.
Apply cycle property:
The uniquely heaviest edge on a cycle cannot belong to any MST.
Conclusion:
Edge (D,A) will never be chosen by Kruskal (it would be considered last), and even if a naive algorithm picked it early, you could always replace it with a lighter edge from the same cycle without disconnecting the graph.
Insight: Cycle property is a powerful pruning rule: spotting a heavy edge on a cycle lets you discard it without computing the full MST.
An MST is a spanning tree (connected, acyclic, includes all vertices) with minimum total edge weight ∑ w(e).
Any spanning tree on n vertices has exactly n−1 edges; cycles represent redundant cost.
Cut property: for any cut (S, V\S), a lightest edge crossing the cut is safe (can appear in some MST).
Cycle property: on any cycle, a heaviest edge can be excluded from some MST; if uniquely heaviest, it is in no MST.
Kruskal: sort edges and add if they connect different components; implement efficiently with DSU/Union-Find.
Prim: grow a single tree; repeatedly add the lightest edge leaving the current vertex set; implement with a min-priority queue.
Kruskal naturally handles disconnected graphs by producing a minimum spanning forest; Prim needs restarting per component.
Cut/cycle properties are classic exchange-argument tools that justify greedy algorithms.
Confusing MST with shortest path trees (Prim is not Dijkstra; it doesn’t minimize distances from a root).
Forgetting MST assumes an undirected graph; directed variants require different algorithms (arborescences).
Implementing Kruskal without DSU (leading to slow cycle checks) or implementing Prim without decrease-key handling (leading to incorrect or inefficient behavior).
Stopping Kruskal too late or too early: an MST must end with exactly |V|−1 accepted edges (for a connected graph).
Easy: Prove that any connected graph’s spanning tree has exactly n−1 edges (where n = |V|).
Hint: Use induction on n, or use the fact that a tree is connected and acyclic: adding an edge creates exactly one cycle; removing an edge disconnects.
One proof by induction:
Base n=1: tree has 0 edges.
Inductive step: assume any tree on n−1 vertices has (n−2) edges. Take a tree T on n vertices. T has at least one leaf v (degree 1). Remove v and its incident edge; the remaining graph is still a tree on n−1 vertices, so it has n−2 edges. Adding back v adds 1 edge, so T has (n−2)+1 = n−1 edges.
Medium: Given vertices {1,2,3,4} and edges with weights: (1,2)=1, (2,3)=1, (3,4)=1, (4,1)=1, (1,3)=2. Find one MST and its total weight. (There are multiple.)
Hint: A spanning tree needs 3 edges. Avoid cycles; prefer weight 1 edges first.
All four cycle edges have weight 1. Pick any three that connect all vertices without forming a cycle. For example: (1,2),(2,3),(3,4). This connects all vertices and has no cycle. Total weight = 1+1+1 = 3. Any choice of three of the four weight-1 cycle edges yields an MST of weight 3.
Hard: Let S be the set of vertices already chosen by Prim’s algorithm at some step. Show that the next edge Prim adds is safe by directly invoking the cut property.
Hint: Define the cut (S, V\S). Argue that Prim selects the minimum-weight edge crossing that cut (via the minimum key).
At a given step, Prim maintains S and for every vertex v ∉ S, key[v] is the minimum weight of any edge (u,v) with u ∈ S. Let u be the extracted vertex with minimum key; let e = (parent[u], u). Then e crosses the cut (S, V\S) and has weight key[u]. For any edge crossing the cut, it ends at some vertex v ∉ S, and its weight is ≥ key[v] by definition of key. Since u minimizes key over all v ∉ S, w(e) = key[u*] is the minimum weight among all edges crossing the cut. By the cut property, a lightest cut-crossing edge is safe; therefore Prim’s next edge is safe.
Prereqs you’re using:
Natural next nodes: