25. Minimum Spanning Trees — Prim & Kruskal
796 words
4 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 25. Minimum Spanning Trees — Prim & Kruskal > **What problem does this solve?** You need to connect all cities in a network with the minimum total cost of cables.

25. Minimum Spanning Trees — Prim & Kruskal
What problem does this solve? You need to connect all cities in a network with the minimum total cost of cables. A minimum spanning tree (MST) connects all vertices with |V|-1 edges while minimizing total weight.
1. MST Properties
| Property | Explanation |
|---|---|
| Definition | A subgraph that connects all vertices with minimum total edge weight |
| Edges | Exactly |
| Uniqueness | If all edge weights are distinct, MST is unique |
| Cycle property | The heaviest edge in any cycle is NOT in the MST |
| Cut property | Lightest edge crossing any cut IS in the MST |
2. Prim's Algorithm — Vertex-based (Greedy)
How It Works
Start from any vertex. Repeatedly add the cheapest edge connecting the tree to a new vertex.
python# runnable def prim(WList): """Prim's MST algorithm. Time: O(V²) naïve, O((V+E) log V) with heap Returns: parent of each vertex in MST """ INF = 1 + max(d for u in WList for (v, d) in WList[u]) visited = {v: False for v in WList} distance = {v: INF for v in WList} parent = {v: -1 for v in WList} visited[0] = True for v, d in WList[0]: distance[v] = d parent[v] = 0 for _ in range(1, len(WList)): # Find unvisited vertex with minimum distance unvisited = [v for v in WList if not visited[v]] if not unvisited: break nextv = min(unvisited, key=lambda v: distance[v]) visited[nextv] = True for v, d in WList[nextv]: if not visited[v] and d < distance[v]: distance[v] = d parent[v] = nextv return parent # Graph: 0-1(2), 0-3(6), 1-2(3), 1-3(8), 1-4(5), 2-4(7) WL = { 0: [(1, 2), (3, 6)], 1: [(0, 2), (2, 3), (3, 8), (4, 5)], 2: [(1, 3), (4, 7)], 3: [(0, 6), (1, 8)], 4: [(1, 5), (2, 7)] } parent = prim(WL) print(f"Prim's MST parent: {parent}") # {0: -1, 1: 0, 2: 1, 3: 0, 4: 1} # Edges: (0-1:2), (0-3:6), (1-2:3), (1-4:5) → total = 16
3. Kruskal's Algorithm — Edge-based (Greedy)
How It Works
Sort all edges by weight. Add edges one by one if they don't create a cycle.
python# runnable def kruskal_naive(WList): """Kruskal's MST (naive: O(V²) for cycle check). Time: O(E log E) for sorting + O(V²) for cycle checks """ # Collect all edges edges = [] for u in WList: for v, d in WList[u]: if u < v: # Avoid duplicates for undirected edges.append((d, u, v)) edges.sort() # Sort by weight # Simple component tracking component = {v: v for v in WList} mst_edges = [] for weight, u, v in edges: if component[u] != component[v]: mst_edges.append((u, v, weight)) old_comp = component[u] new_comp = component[v] # Merge components for w in WList: if component[w] == old_comp: component[w] = new_comp return mst_edges mst = kruskal_naive(WL) print(f"Kruskal's MST: {mst}") # [(0, 1, 2), (1, 2, 3), (1, 4, 5), (0, 3, 6)] # Total weight = 2 + 3 + 5 + 6 = 16
4. Union-Find Optimized Kruskal
Using the union-find data structure (next section) for O(m log n) performance.
python# runnable class QuickUnion: 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 compression x = self.parent[x] return x def union(self, a, b): ra, rb = self.find(a), self.find(b) if ra == rb: return if self.size[ra] < self.size[rb]: ra, rb = rb, ra # Ensure ra has larger size self.parent[rb] = ra self.size[ra] += self.size[rb] def kruskal_optimized(WList): """Kruskal with union-find for O(E log V) performance.""" edges = [] for u in WList: for v, d in WList[u]: if u < v: edges.append((d, u, v)) edges.sort() uf = QuickUnion(len(WList)) mst_edges = [] for weight, u, v in edges: if uf.find(u) != uf.find(v): uf.union(u, v) mst_edges.append((u, v, weight)) return mst_edges print(f"Kruskal optimized: {kruskal_optimized(WL)}")
5. Comparison: Prim vs Kruskal
| Feature | Prim's | Kruskal's |
|---|---|---|
| Strategy | Grow a single tree | Merge multiple trees |
| Data structure | Priority queue (heap) | Union-find |
| Best for | Dense graphs | Sparse graphs |
| Time (simple) | O(V²) | O(E log E) |
| Time (heap/UF) | O((V+E) log V) | O(E log V) |
| Correctness | Cut property | Cycle property |
Practice Questions
Q1. Run Prim's from vertex 0 on: 0-1(1), 0-2(4), 1-2(2), 1-3(6), 2-3(3). Show MST edges.
Q2. Run Kruskal's on the same graph.
Q3. If all edge weights are equal, how many different MSTs can exist?
AnswersA1. Prim: Start at 0. Add 0-1(1). Add 1-2(2) [cheaper than 0-2(4)]. Add 2-3(3) [cheaper than 1-3(6)]. MST: (0-1:1), (1-2:2), (2-3:3). Total: 6.A2. Sorted edges: (0-1:1), (1-2:2), (2-3:3), (0-2:4), (1-3:6). Add (0-1), (1-2), (2-3) — same as Prim. (0-2) creates cycle, skip. (1-3) creates cycle, skip.A3. if all weights equal, any spanning tree is minimum. Number of MSTs = number of spanning trees (can be exponential). Join Discord Previous24. Bellman-Ford & Floyd-WarshallNext26. Union-Find (Disjoint Set)