Kruskal’s Algorithm
Kruskal’s algorithm computes a minimum spanning tree (MST) of a connected, undirected, weighted graph in
O(E log E)time. The strategy is brutally simple: sort all edges by weight; greedily add the cheapest edge that doesn’t form a cycle, until you have V-1 edges. Cycle detection is done with Union-Find in essentially constant time per check, so the bottleneck is the initial sort. Kruskal’s is the canonical example of a provably optimal greedy algorithm — and a frequent interview question because it elegantly combines two foundational tools (sorting + DSU).
1. Intuition — Cheapest Roads First
Imagine you’re a town planner connecting V villages with paved roads. You have a list of every possible road and its cost. You need to make sure every village is reachable, while spending as little as possible.
Kruskal’s strategy:
- Sort all candidate roads from cheapest to most expensive.
- For each road in order:
- If building it would connect two villages that are not yet connected (directly or via existing roads), build it.
- If both villages are already connected (perhaps via a longer chain), skip this road — it would form a redundant cycle.
- Stop when every village is connected (you’ve built exactly V-1 roads).
The result is the minimum spanning tree: a subset of edges that connects all vertices, has no cycles (it’s a tree), and has minimum total weight.
The “is it already connected?” check uses Union-Find — exactly the data structure designed for this question.
2. Tiny Worked Example
Graph (undirected, weighted):
1
A ──── B
│ \ │
4│ 2\ 3│
│ \ │
D ──── C
5
Edges: A-B(1), A-C(2), A-D(4), B-C(3), C-D(5).
Sort by weight: A-B(1), A-C(2), B-C(3), A-D(4), C-D(5).
Process:
- A-B (weight 1): A and B are in different components. Add. Components: {A,B}, {C}, {D}.
- A-C (weight 2): A and C are in different components. Add. Components: {A,B,C}, {D}.
- B-C (weight 3): B and C are in the same component (both in {A,B,C}). Skip — would create cycle A-B-C-A.
- A-D (weight 4): A and D are in different components. Add. Components: {A,B,C,D}. Done (V-1 = 3 edges).
- (We don’t even examine C-D.)
MST edges: A-B, A-C, A-D. Total weight: 1 + 2 + 4 = 7.
Every other spanning tree (e.g., A-B, A-C, C-D = 1+2+5 = 8, or A-B, B-C, A-D = 1+3+4 = 8) is more expensive. Kruskal’s found the optimum.
3. Pseudocode
kruskal(num_vertices, edges):
sort edges by weight ascending
uf := UnionFind(num_vertices)
mst := empty list
total_weight := 0
for each (u, v, w) in sorted edges:
if uf.union(u, v): # returns True iff actually merged
mst.append((u, v, w))
total_weight += w
if length(mst) == num_vertices - 1:
break # optimization: done
if length(mst) < num_vertices - 1:
return None # graph is disconnected — no MST exists
return mst, total_weight
4. Python Implementation
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # path halving
x = self.parent[x]
return x
def union(self, x, y):
rx, ry = self.find(x), self.find(y)
if rx == ry: return False
if self.size[rx] < self.size[ry]: rx, ry = ry, rx
self.parent[ry] = rx
self.size[rx] += self.size[ry]
return True
def kruskal(num_vertices, edges):
"""edges: list of (u, v, w) tuples (undirected, weighted)."""
edges_sorted = sorted(edges, key=lambda e: e[2])
uf = UnionFind(num_vertices)
mst = []
total = 0
for u, v, w in edges_sorted:
if uf.union(u, v):
mst.append((u, v, w))
total += w
if len(mst) == num_vertices - 1:
break
if len(mst) < num_vertices - 1:
return None # disconnected
return mst, total5. Why It Works (Cut Property + Exchange Argument)
The proof of correctness uses the cut property of MSTs:
Cut property: For any partition (cut) of the vertices into two non-empty sets, the lightest edge crossing the cut is in some MST.
Proof of cut property (exchange argument). Suppose e is the lightest edge across some cut, and suppose for contradiction that no MST contains e. Take any MST T; adding e creates a cycle (since T is connected). The cycle must cross the cut at least one other time — call that crossing edge f. Since e is the lightest edge crossing the cut, weight(e) ≤ weight(f). Remove f from T ∪ {e} to get a new spanning tree T' with weight(T') ≤ weight(T). So T' is also an MST — and it contains e. Contradiction.
Why Kruskal’s algorithm is correct: every edge it adds is the lightest edge crossing the cut between its current component and the rest of the graph (because we examine edges in weight order, and we only add an edge if its endpoints are in different components). By the cut property, every edge Kruskal adds is in some MST. So the final tree is an MST.
The proof is a model of clarity — it’s the kind of argument interviewers love asking candidates to reproduce.
6. Complexity
| Step | Cost |
|---|---|
| Sort edges | O(E log E) |
| Initialize Union-Find | O(V) |
E iterations of union/find | O(E · α(V)) ≈ O(E) |
| Total | O(E log E) = O(E log V) |
Note: log E ≤ log V² = 2 log V, so O(E log E) = O(E log V). The two are interchangeable.
The sort dominates. If edges are pre-sorted (or come from a structure that allows linear-time sorting like radix sort on integer weights), Kruskal becomes essentially O(E α(V)).
Space: O(V) for the Union-Find structure, O(E) for the edge list (if not in place).
7. Comparison with Prim’s Algorithm
The other classic MST algorithm is Prim’s Algorithm: start from any vertex; repeatedly add the cheapest edge from the current MST to a vertex not yet in the MST.
| Property | Kruskal | Prim |
|---|---|---|
| Approach | Edge-centric (sort, scan) | Vertex-centric (heap-based grow) |
| Data structure | Union-Find + sort | Min-heap |
| Time (binary heap Prim) | O(E log V) | O((V+E) log V) |
| Time (Fibonacci heap Prim) | n/a | O(E + V log V) |
| Time (matrix Prim) | n/a | O(V²) — best for dense graphs |
| Best for | Sparse graphs, when Union-Find is natural | Dense graphs, single-source-style growth |
| Disconnected graphs | Returns minimum spanning forest naturally | Stops at the first component |
For most interview problems, Kruskal is the cleaner code: sort + DSU + 5 lines. Prim is preferred for very dense graphs (E ≈ V²) because the matrix variant is O(V²) vs Kruskal’s O(V² log V).
8. Variants
8.1 Maximum Spanning Tree
Sort edges in descending order. Same algorithm otherwise. Useful when “weight” is something we want to maximize (reliability, throughput).
8.2 Minimum Spanning Forest
If the graph is disconnected, Kruskal’s natural output is the minimum spanning forest — one MST per connected component. Just don’t stop at V-1 edges; let the loop finish.
8.3 Second-Best MST
The MST whose total weight is the smallest strictly greater than the MST’s. Algorithm: build MST; for each non-MST edge e, find the heaviest edge on the cycle it creates, swap them, take the minimum total. O(VE) straightforward; O(E log V) with care.
8.4 Bottleneck Spanning Tree
A spanning tree minimizing the maximum edge weight (rather than sum). It turns out: every MST is also a bottleneck ST. Specialized linear-time algorithms exist (Camerini’s), but for interview purposes, just compute the MST.
8.5 Constrained MST
E.g., “MST with at most k red edges.” Significantly harder; usually requires Lagrangian relaxation or specialized algorithms. Beyond standard interview scope.
9. Use Cases
9.1 Network Design
Lay cable, road, water pipe, fiber to connect a set of locations at minimum total cost. The original motivating application.
9.2 Clustering (Single-Linkage)
Build the MST of the data-point graph (distances as edge weights). Removing the k-1 heaviest MST edges produces k clusters with the property that within-cluster distances are all smaller than between-cluster distances. Equivalent to single-linkage hierarchical clustering. Used in image segmentation, anomaly detection, and recommender systems.
9.3 Approximation Algorithms
The MST is the basis of approximation algorithms for Travelling Salesman (the Christofides-Serdyukov algorithm has a 3/2-approximation guarantee and starts with the MST).
9.4 Boruvka Step in Parallel MST
Boruvka’s algorithm uses a parallel-friendly variation: each component finds its cheapest outgoing edge in parallel, then all are added at once. Used in distributed/parallel implementations.
10. Common Interview Problems
| Problem | Pattern |
|---|---|
| LC 1584 — Min Cost to Connect All Points | Kruskal on Manhattan-distance complete graph |
| LC 1135 — Connecting Cities With Minimum Cost | Plain MST |
| LC 1168 — Optimize Water Distribution in a Village | Add virtual source for “well” costs, then MST |
| LC 1489 — Find Critical and Pseudo-Critical Edges in MST | MST with edge ablation |
| Network reliability / minimum cost backbone | Direct MST application |
11. Pitfalls
11.1 Forgetting to Sort
Kruskal must process edges in weight order. Forgetting sorted(...) produces a spanning tree (since DSU still prevents cycles), but it’s not the minimum.
11.2 Not Checking for Disconnected Graph
If the graph is disconnected, Kruskal terminates with fewer than V-1 edges. Always check this case; some problems require returning -1, others want the spanning forest.
11.3 Bad DSU Implementation
Without path compression + union by rank/size, your Union-Find ops are O(log V) (or worse), pushing total time to O(E log V · log V). Use the proper DSU.
11.4 Counting Edges vs Vertices
The MST has exactly V-1 edges (not V). Off-by-one when checking the early-termination condition is a common bug.
11.5 Including Self-Loops
Edges from a vertex to itself (u == v) are useless in MST — union(u, u) returns False, no harm done. But if your input has many self-loops, the sort wastes time. Filter them out first.
11.6 Multi-Edges
If multiple edges exist between the same pair, Kruskal naturally takes only the cheapest (the first one sorted). No special handling needed.
11.7 Using Kruskal When Prim Is Better
For dense graphs (E ≈ V²), the O(E log V) of Kruskal might be worse than the O(V²) of matrix-Prim. Estimate before committing.
12. Diagram — Kruskal’s Greedy March
flowchart LR S0[Sort edges by weight] --> P1[Pick cheapest edge] P1 --> Q{Endpoints in same component?} Q -- yes --> Skip[Skip → cycle] Q -- no --> Add[Add to MST<br/>union endpoints] Skip --> Next[Next edge] Add --> Check{V-1 edges yet?} Check -- yes --> Done[MST complete] Check -- no --> Next Next --> Q
What this diagram shows. The control flow: sort once, then iterate edges in order, using Union-Find as the “is this a cycle?” oracle. Each edge is either added (decreasing component count) or skipped (would close a cycle). The algorithm halts the moment we have V-1 edges (the count needed for a spanning tree on V vertices).
13. Why This Greedy Algorithm Works (And Most Don’t)
Most “greedy” algorithms fail — local optimality doesn’t imply global optimality. Kruskal works because of a deep structural property of MST: the matroid structure. The set of forests on a graph forms a matroid; matroid theory tells us that the greedy algorithm produces an optimal solution iff the structure is a matroid. MSTs are the canonical matroid example.
You don’t need matroid theory for interviews — but if pressed on “why does this greedy work?” the answer is “the MST problem has the matroid optimality property; the cut-property proof above shows it concretely.” This level of depth distinguishes a senior candidate.
14. Open Questions
- Is there a sub-
O(E log V)MST? Yes — Karger’s randomized algorithm gives expectedO(E). Chazelle (2000) gave deterministicO(E α(V)). Both are theoretical; not used in practice. - Boruvka vs Kruskal vs Prim — which is “the right one”? Depends on graph density and parallelism. For the typical interview, Kruskal wins on simplicity.
15. See Also
- Union-Find — Kruskal’s core data structure
- Prim’s Algorithm — alternative MST algorithm
- Borůvka’s Algorithm — parallelizable MST
- Greedy Algorithms — Proof Techniques
- Big-O Notation
- SWE Interview Preparation MOC