Pattern #36
Minimum Spanning Tree
AdvancedKruskal / Prim: connect all nodes at minimum total edge cost.
When to use
Use when you need to connect all nodes of a graph at minimum total edge weight. Kruskal (sort edges + union-find) for sparse graphs; Prim (priority queue) for dense.
Recognition cues
- Connect all nodes at minimum total cost
- MST / minimum spanning tree
- Cluster / connect cities cheapest
- Kruskal (sort edges) or Prim (greedy from a node)
Common pitfalls
- Kruskal needs union-find for cycle detection
- Prim: stale priority queue entries
- Confusing MST with shortest path (MST minimizes total, not per-path)
90-second recognition drill
Which pattern fits best?
- Connect all nodes at minimum total cost
- MST / minimum spanning tree
- Cluster / connect cities cheapest
Interactive
Mental model
A full worked walkthrough of the invariant. Pause, scrub the dots, or use ← →. Aim to narrate each step yourself.
Kruskal: sort edges, skip cycles
Connect all nodes at minimum total weight. Edges: AB1, BC2, AC3, BD4, CD5. MST ≠ shortest path.
How to think about it
An MST connects all vertices of a weighted undirected graph at the minimum total edge weight. It is a tree (no cycles) spanning all nodes. The key insight: if you sort edges by weight and add each edge unless it would form a cycle, you get the MST, that is Kruskal. Cycle detection at scale means union-find: two endpoints in the same component → adding the edge makes a cycle, skip it.
Prim grows the MST from a single seed: maintain a min-heap of edges crossing the cut (one endpoint in the tree, one out). Pop the cheapest, add the out node, push its edges. Prim shines on dense graphs (E ~ V²) where sorting all E edges is wasteful. On sparse graphs (E ~ V), Kruskal’s sort + union-find is simpler and equally fast.
Template shapes
| Shape | Core move | Example |
|---|---|---|
| Kruskal | Sort edges; union-find skip cycles | LC 1135, LC 1584 |
| Prim | Min-heap of crossing edges; grow from seed | LC 1168 |
| MST with constraints | Binary search on answer / filter edges | LC 1489 |
| Offline MST queries | Sort queries + edges; union-find answers | LC 1724 |
Complexity baseline
Kruskal: O(E log E) (sort) + near-O(E) union-find. Prim with heap: O((V + E) log V). For dense graphs, Prim without a heap is O(V²).
From template to problem
- Confirm the graph is undirected and weights are comparable (MST does not handle negative specially).
- Sparse (E ~ V)? Kruskal. Dense (E ~ V²)? Prim.
- For Kruskal: sort edges, iterate, union-find skip cycles, stop at V-1 edges.
- The MST minimizes total edge weight, not any individual path, do not confuse with shortest-path.
Template
Same skeleton in TypeScript, Python, and C#. Adapt the invariant; keep the structure.
/** MST template: Kruskal with union-find. */
export function kruskalMST(
n: number, edges: [number, number, number][],
): { weight: number; edges: [number, number, number][] } {
const sorted = [...edges].sort((a, b) => a[2]! - b[2]!);
const parent = new Array<number>(n).fill(0).map((_, i) => i);
function find(x: number): number {
if (parent[x] !== x) parent[x] = find(parent[x]!);
return parent[x]!;
}
function union(a: number, b: number): boolean {
const ra = find(a), rb = find(b);
if (ra === rb) return false;
parent[ra] = rb;
return true;
}
let total = 0;
const mst: [number, number, number][] = [];
for (const e of sorted) {
if (mst.length === n - 1) break;
if (union(e[0]!, e[1]!)) {
mst.push(e);
total += e[2]!;
}
}
return { weight: total, edges: mst };
}/** MST template: Kruskal with union-find. */
export function kruskalMST(
n: number, edges: [number, number, number][],
): { weight: number; edges: [number, number, number][] } {
const sorted = [...edges].sort((a, b) => a[2]! - b[2]!);
const parent = new Array<number>(n).fill(0).map((_, i) => i);
function find(x: number): number {
if (parent[x] !== x) parent[x] = find(parent[x]!);
return parent[x]!;
}
function union(a: number, b: number): boolean {
const ra = find(a), rb = find(b);
if (ra === rb) return false;
parent[ra] = rb;
return true;
}
let total = 0;
const mst: [number, number, number][] = [];
for (const e of sorted) {
if (mst.length === n - 1) break;
if (union(e[0]!, e[1]!)) {
mst.push(e);
total += e[2]!;
}
}
return { weight: total, edges: mst };
}- 1#1168 Optimize Water Distribution in VillageGuidehard
- 2#1135 Connecting Cities With Minimum CostGuidemedium
- 3#1489 Find Critical and Pseudo-Critical Edges in MSTGuidehard
- 4#1584 Min Cost to Connect All PointsGuidemedium
- 5#1724 Checking Existence of Edge Length Limited Paths IIGuidehard
- 6#2093 Minimum Cost to Reach City With DiscountsGuidehard