Minimum spanning tree: Prim, Kruskal and union-find
The tree that connects all vertices of a weighted undirected graph at the lowest cost. You learn to trace Kruskal with union-find and Prim with a step table, and to solve quiz-style questions.
You are laying cable on a campus. There is a cost between every pair of buildings. You want to connect all the buildings to each other, but with the least cable. The answer is the minimum spanning tree (MST): the set of edges that contains all vertices, has no cycle and has the smallest total weight. Both algorithms of this week are greedy. Exam and quiz questions are almost always tracing questions: “write the edges Kruskal accepts in order”, “start Prim from A, give the order”, “what is the MST weight?”, “show the union-find array”.
Golden rules:
- An MST has exactly V − 1 edges. Fewer does not connect, more forms a cycle.
- Kruskal selects edges: start from the lightest edge, take it if it does not form a cycle.
- Prim grows a vertex set: a single tree, add the vertex that connects to it most cheaply at each step.
- Prim’s table is like Dijkstra’s but does not keep the path total, it keeps only the weight of a single edge.
- The cycle check is done with union-find: if the two ends are in the same set, reject.
Definition and two properties
Spanning tree: a subgraph of a connected, undirected graph that contains all its vertices and has no cycle. V−1 edges for V vertices. The minimum one is the one with the smallest sum of edge weights.
The correctness of all MST algorithms rests on two properties:
- Cut property: Divide the vertices into any two groups. Of the edges that cross between the two groups, the lightest (if unique) is in every MST.
- Cycle property: The heaviest edge in a cycle (if unique) is in no MST.
Prim uses the cut property directly (cut = tree / the rest). Kruskal uses the fact that every edge it rejects is the heaviest edge of a cycle.
The working graph
Graph G (undirected). The edges sorted by weight:
edge weight edge weight
A-C 1 B-C 7
C-D 2 C-E 8
A-B 3 D-F 9
D-E 4 B-E 10
B-D 5
E-F 6
Kruskal’s algorithm
- Sort the edges by increasing weight (listing order on ties).
- Each vertex starts in its own set.
- For the next edge (u, v): if
find(u) != find(v), accept the edge andunion(u, v). Otherwise reject it (it forms a cycle). - Stop when V−1 edges have been accepted.
edge decision sets afterwards
A-C 1 ACCEPT {A,C} {B} {D} {E} {F}
C-D 2 ACCEPT {A,C,D} {B} {E} {F}
A-B 3 ACCEPT {A,B,C,D} {E} {F}
D-E 4 ACCEPT {A,B,C,D,E} {F}
B-D 5 REJECT B and D are in the same set
E-F 6 ACCEPT {A,B,C,D,E,F} → 5 edges, stop
The program’s parent array (in the order A..F, each element is the parent of that vertex):
A-C 1 ACCEPT parent [A B A D E F]
C-D 2 ACCEPT parent [A B A A E F]
A-B 3 ACCEPT parent [A A A A E F]
D-E 4 ACCEPT parent [A A A A A F]
B-D 5 reject parent [A A A A A F]
E-F 6 ACCEPT parent [A A A A A A]
Kruskal total 16 edges 5
The MST:
B
| 3
A
| 1
C
| 2
D
| 4
E
| 6
F
In this example the MST came out as a chain; it is not always like this.
PITFALL · trying to check the remaining edges too. Stop when the 5th edge is accepted. Edges like B-C 7, C-E 8 would form cycles anyway; do not waste time extending the table needlessly in the exam. But if the question says “list all rejected edges”, write the rest as rejected too.
Union-find (disjoint set)
The cycle check of Kruskal is made fast with this structure. Each set is a tree; parent[x] is the parent of x, and at the root parent[r] = r.
- find(x): go up to the root, return the root.
- union(a, b): find the two roots, attach one under the other.
Two improvements:
- Union by size (or rank): attach the root of the smaller tree under the larger one. The height of the tree does not exceed .
- Path compression: during find, attach every node on the path directly to the root. The following finds take one step.
Together they give practically constant time per operation (, inverse Ackermann).
Trace: union by size on 0..7
Rule: if the sizes are equal, the root of the second argument goes under the first.
union(0,1) parent [0, 0, 2, 3, 4, 5, 6, 7]
union(2,3) parent [0, 0, 2, 2, 4, 5, 6, 7]
union(4,5) parent [0, 0, 2, 2, 4, 4, 6, 7]
union(6,7) parent [0, 0, 2, 2, 4, 4, 6, 6]
union(0,2) parent [0, 0, 0, 2, 4, 4, 6, 6]
union(4,6) parent [0, 0, 0, 2, 4, 4, 4, 6]
union(0,4) parent [0, 0, 0, 2, 0, 4, 4, 6]
before after find(7)
0 0
/ | \ / / | \ \
1 2 4 1 2 4 6 7
| | \ | |
3 5 6 3 5
|
7
path from 7 before find: 7 6 4 0
find(7) = 0 parent after [0, 0, 0, 2, 0, 4, 0, 0]
PITFALL · path compression changes only those on the path. After find(7), 6 and 7 were attached to the root. 3 and 5 were not on the path, and their parents (2 and 4) did not change.
Prim’s algorithm
- The starting vertex has value 0, the others ∞.
- From those not in the tree, choose the vertex with the smallest value and add it to the tree (the smaller label on a tie).
- For each neighbor u of the new vertex: if
w(v,u) < d[u]thend[u] = w(v,u),from[u] = v. - Repeat until all vertices are added.
The only difference from Dijkstra is in step 3: Dijkstra compares d[v] + w, Prim compares only w. Prim’s d[u] value is “the cheapest edge to connect u to the tree”, not the distance from a source.
Starting from A in G (* is in the tree):
add A B C D E F edge
A 0* 3 1 ∞ ∞ ∞ -
C 0* 3 1* 2 8 ∞ A-C 1
D 0* 3 1* 2* 4 9 C-D 2
B 0* 3* 1* 2* 4 9 A-B 3
E 0* 3* 1* 2* 4* 6 D-E 4
F 0* 3* 1* 2* 4* 6* E-F 6
Prim order A C(via A-C 1) D(via C-D 2) B(via A-B 3) E(via D-E 4) F(via E-F 6) total 16
The same MST, a different order of addition. If you had started from F, the order would be F, E, D, C, A, B, but the edges and the total of 16 would be the same.
TIP · Prim or Kruskal? In a sparse graph, and when an edge list is given, Kruskal is practical. In a dense graph (adjacency matrix) the version of Prim is good. Write the order of whichever the exam asks for; do not mix the two.
Is the MST unique?
If all edge weights are distinct, the MST is unique. If there are equal weights, there can be more than one MST, but the total of all of them is the same.
Graph H: P-Q 2, P-R 3, Q-R 2, Q-S 4, R-S 4, R-T 5, S-T 1.
S-T 1 ACCEPT
P-Q 2 ACCEPT
Q-R 2 ACCEPT
P-R 3 reject
Q-S 4 ACCEPT
Kruskal total 9 edges 4
If R-S (which is also 4) were taken instead of Q-S, {P, Q, R} and {S, T} would still be connected and the total would again be 9. There are two valid MSTs.
Quiz rehearsal
In a quiz, usually a single graph is given for 15-20 minutes and three things are asked. Try yourself against the clock (graph G):
1) The edges Kruskal accepts, in order?
2) The order of addition if Prim starts from B?
3) The MST weight?
answers:
1) A-C, C-D, A-B, D-E, E-F
2) B, A(3), C(1), D(2), E(4), F(6)
3) 16
Checking answer 2: from B, A=3, D=5, C=7, E=10. A is added, C becomes 1. C is added, D=2, E=8. D is added, E=4, F=9. E is added, F=6. Do not do it without drawing the table on paper; the most common mistake when going from memory is to pick D=5 when starting from B.
Complexity summary
| Algorithm | Data structure | Time |
|---|---|---|
| Kruskal | sorting + union-find | |
| Prim | binary heap | |
| Prim | array scan | |
| union-find | size + compression | per operation |
Common mistakes
| Wrong idea | The truth |
|---|---|
| In Prim, d[u] = d[v] + w | In Prim, d[u] = w(v,u); the path total is Dijkstra |
| MST = the tree of shortest paths from a source | Different things; the MST minimizes the total weight, Dijkstra minimizes each path |
| Kruskal keeps a connected tree at every step | Kruskal keeps a forest and merges it at the end; the single tree is Prim |
| In Prim, choose a neighbor of the last added | The lightest of the outgoing edges of ALL vertices in the tree |
| In union, attach a directly to b | Attach the ROOTS: parent[find(b)] = find(a) |
| A different MST with equal weights = wrong | Valid if the total is the same |
| V edges in an MST | V − 1 edges |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Graph G (undirected): A-B 3, A-C 1, B-C 7, B-D 5, C-D 2, C-E 8, D-E 4, D-F 9, E-F 6, B-E 10. How many edges does its MST have, and what is the total weight?
Show answer
A tree with 6 vertices has V-1 = 5 edges. Kruskal takes A-C 1, C-D 2, A-B 3, D-E 4, E-F 6: 1+2+3+4+6 = 16.
Run Kruskal's algorithm on G. In which order are edges accepted?
Show answer
Sort the edges by weight: A-C 1, C-D 2, A-B 3, D-E 4, B-D 5, E-F 6, B-C 7, C-E 8, D-F 9, B-E 10.
A-C, C-D, A-B, D-E are accepted. B-D 5: B and D are already in the same set (through A-B and A-C-D), rejected. E-F 6 is accepted. That makes 5 edges, stop.
Which edge is the first one Kruskal rejects on G, and why?
Show answer
When B-D arrives, find(B) = find(D) = A. This edge would close the cycle B-A-C-D-B. The cycle property: the heaviest edge of a cycle (here B-D 5 > 3, 1, 2) does not have to be in the MST.
Run Prim's algorithm on G starting from A. In which order are vertices added, and through which edge?
Show answer
add A B C D E F
A 0* 3 1 ∞ ∞ ∞
C 0* 3 1* 2 8 ∞
D 0* 3 1* 2* 4 9
B 0* 3* 1* 2* 4 9
E 0* 3* 1* 2* 4* 6
F 0* 3* 1* 2* 4* 6*At each step the vertex that connects to the tree most cheaply is chosen. When D is added, B=3 and E=4 compete; B wins.Run Prim on G starting from F. Give the order of added vertices.
Show answer
F: E=6, D=9. E is added (6): D=4, C=8, B=10. D is added (4): C=2, B=5. C is added (2): A=1. A is added (1): B=3. B is added (3).
The edges F-E 6, E-D 4, D-C 2, C-A 1, A-B 3: again 16. The starting vertex changes the order, but in a graph with distinct weights it does not change the MST.
Cut property: in G, consider the cut S = {A, B} versus the rest {C, D, E, F}. Which edges cross the cut, and which one must be in every MST?
Show answer
The edges that cross the cut are those with one end in S and the other outside. The cut property: the lightest edge crossing the cut (if it is unique) is in every MST. Prim also takes the lightest edge of the cut between the 'tree' and 'the rest' at every step.
Union-find on 0..7 with union by size (on equal size, the second root goes under the first) and path compression. Perform union(0,1), union(2,3), union(4,5), union(6,7), union(0,2), union(4,6), union(0,4). Give the parent array.
Show answer
The first four unions: 1→0, 3→2, 5→4, 7→6. union(0,2): both roots have size 2, 2→0. union(4,6): 6→4. union(0,4): the sizes are 4 and 4, 4→0.
0
/ | \
1 2 4
| | \
3 5 6
|
7In these unions the find calls were already at roots, so compression changed nothing.Continuing from the previous state, call find(7) with path compression. What does it return, and what is the parent array afterwards?
Show answer
The path is 7 → 6 → 4 → 0. Path compression attaches every node on the path directly to the root: parent[7] = 0, parent[6] = 0 (4 is already attached to 0). The next find(7) finishes in one step.
Graph H: P-Q 2, P-R 3, Q-R 2, Q-S 4, R-S 4, R-T 5, S-T 1. What is the MST weight? Is the MST unique?
Show answer
Kruskal (listing order on ties): S-T 1, P-Q 2, Q-R 2 accepted; P-R 3 rejected (cycle); Q-S 4 accepted. Total 1+2+2+4 = 9.
If R-S 4 were earlier in the list, it would have been taken instead of Q-S; still 9. Two different MSTs, the same weight. If all edge weights are distinct the MST is unique; with equal weights there can be more than one.
Give the running times: (a) Kruskal with sorting and union-find, (b) Prim with a binary heap, (c) Prim with an adjacency matrix and array scan.
Show answer
(a) Sorting dominates: ; since , , that is, . The union-find operations are almost constant. (b) The same structure as Dijkstra. (c) At each step V elements are scanned, V steps: good for dense graphs.
Write int find(int x) with path compression and boolean union(int a, int b) with union by size, using arrays parent[] and size[]. union returns false if a and b are already in the same set.
Show answer
static int[] parent, size;
static int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]); // path compression
return parent[x];
}
static boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false; // same set: cycle
if (size[ra] < size[rb]) { int t = ra; ra = rb; rb = t; }
parent[rb] = ra; // the smaller goes under the larger
size[ra] += size[rb];
return true;
}Initially parent[i] = i, size[i] = 1. Together the two give almost constant (inverse Ackermann) time per operation.Write int kruskal(int n, int[][] edges) that returns the MST weight. Each edge is {u, v, w}. Use the find/union methods from the previous question.
Show answer
static int kruskal(int n, int[][] edges) {
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; }
int[][] es = edges.clone();
Arrays.sort(es, (x, y) -> Integer.compare(x[2], y[2]));
int total = 0, used = 0;
for (int[] e : es) {
if (union(e[0], e[1])) {
total += e[2];
if (++used == n - 1) break;
}
}
return total;
}It returns 16 for graph G (A..F = 0..5). If the graph is not connected, used stays below n-1; then there is no MST, only a spanning forest.Verification: Kruskal's accept/reject order and the parent array at each step, Prim step tables (from A and from F), the equal-weight alternative MST in the second graph, and the union by size + path compression trace were run with verify/SEN2212/W13.java (java W13.java). The find/union and kruskal methods were tested in verify/SEN2212/WMethods11to14.java.