Topics › Graphs › MinimumSpanningTree/Disjoint Set and Problems
Find the MST weight
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given a connected, undirected, weighted graph with V vertices numbered 0 to V - 1 and its edges [u, v, w], return the sum of the edge weights of a minimum spanning tree: V - 1 edges that connect every vertex with the smallest possible total weight. On the A2Z sheet this row is solved with Kruskal’s algorithm, the edge-centric counterpart to Prim’s. If the graph is disconnected, the Java methods return -1.
Constraints (GeeksforGeeks “Minimum Spanning Tree”): 2 ≤ V ≤ 1000, V - 1 ≤ E ≤ V(V-1)/2, 1 ≤ w ≤ 1000.
Example 1
V = 3, edges = [[0,1,5], [1,2,3], [0,2,1]]
answer = 4 // take 0-2 (1) and 1-2 (3); 0-1 (5) would close a cycle
Example 2
V = 6, edges = [[5,4,9], [5,1,4], [4,1,1], [4,3,5], [1,2,2], [2,3,3], [2,0,7], [1,0,4]]
answer = 14 // 4-1 (1), 1-2 (2), 2-3 (3), 5-1 (4), 1-0 (4)
The idea behind Kruskal’s algorithm
Sort all edges from lightest to heaviest and walk through them. Take an edge if it joins two vertices that are not yet connected by the edges taken so far. Skip it if they already are, because then the edge would close a cycle. Stop after V - 1 edges have been taken.
Why does this produce a minimum spanning tree? When Kruskal’s takes edge e = (u, v), u and v lie in different components of the current forest. Let S be u’s component. Every edge lighter than e has already been examined: it was either taken (and so lies inside a component, not crossing out of S) or skipped (because its endpoints were already in the same component). So e is the lightest edge crossing the cut between S and the rest of the graph, and the cut property says it belongs to some MST. When Kruskal’s skips an edge, that edge would close a cycle, and because edges arrive in sorted order it is at least as heavy as every other edge on that cycle, so by the cycle property dropping it costs nothing.
The only question is how to answer “are u and v already connected?” quickly. That is the whole difference between the two approaches.
Approach 1: Kruskal with a DFS connectivity check
Intuition. Keep the chosen edges in an adjacency list (a forest). For each candidate edge, run a DFS from u inside that forest and see whether it reaches v. It is direct and obviously correct, but it repeats a lot of work: every check re-explores components that have not changed.
Algorithm.
- Sort a copy of the edges by weight.
- For each edge
(u, v, w): if a DFS over the chosen edges reachesvfromu, skip it. Otherwise add it to the forest in both directions, addwto the total, and count it. - Return the total if
V - 1edges were taken, else-1.
static int kruskalWithDfs(int n, int[][] edges) {
int[][] sorted = edges.clone();
Arrays.sort(sorted, (a, b) -> Integer.compare(a[2], b[2]));
List<List<Integer>> forest = new ArrayList<>();
for (int i = 0; i < n; i++) forest.add(new ArrayList<>());
int total = 0, used = 0;
for (int[] e : sorted) {
if (connected(forest, e[0], e[1])) continue; // already joined: this edge would close a cycle
forest.get(e[0]).add(e[1]);
forest.get(e[1]).add(e[0]);
total += e[2];
used++;
}
return used == n - 1 ? total : -1;
}
static boolean connected(List<List<Integer>> forest, int s, int t) {
boolean[] seen = new boolean[forest.size()];
Deque<Integer> stack = new ArrayDeque<>();
stack.push(s);
seen[s] = true;
while (!stack.isEmpty()) {
int u = stack.pop();
if (u == t) return true;
for (int v : forest.get(u)) {
if (!seen[v]) {
seen[v] = true;
stack.push(v);
}
}
}
return false;
}
Complexity. Sorting costs O(E log E). Each of the E checks is a DFS over a forest with fewer than V edges, so it costs O(V). Total O(E log E + E · V). For V = 1000 and E ≈ 500,000 that is about 5 × 10^8 steps, too slow. Space O(V + E).
Approach 2: Optimal, Kruskal with a disjoint set
Intuition. “Are u and v in the same component?” and “merge their components” are exactly the two operations a disjoint set union supports. With union by size and path compression, each costs amortised O(α(V)), effectively constant. We let union return false when the two vertices already share a root. Then one call both answers the question and performs the merge. We can also stop as soon as V - 1 edges are taken, since the tree is complete and no later edge can be used.
Algorithm.
- Sort a copy of the edges by weight. Create a DSU on
Velements. - For each edge
(u, v, w): ifunion(u, v)returnsfalse, skip the edge. Otherwise addwto the total, count the edge, and stop once the count reachesV - 1. - Return the total if
V - 1edges were taken, else-1.
static int kruskalWithDsu(int n, int[][] edges) {
int[][] sorted = edges.clone();
Arrays.sort(sorted, (a, b) -> Integer.compare(a[2], b[2]));
DSU dsu = new DSU(n);
int total = 0, used = 0;
for (int[] e : sorted) {
if (!dsu.union(e[0], e[1])) continue; // same component: skip
total += e[2];
if (++used == n - 1) break; // a spanning tree is complete
}
return used == n - 1 ? total : -1;
}
static class DSU {
final int[] parent, size;
DSU(int n) {
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int x) {
if (parent[x] == x) return x;
return parent[x] = find(parent[x]);
}
/** Merge the sets of a and b; false if they were already the same set. */
boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (size[ra] < size[rb]) {
int tmp = ra;
ra = rb;
rb = tmp;
}
parent[rb] = ra;
size[ra] += size[rb];
return true;
}
}
Dry run on Example 2. Sorted edges: 4-1 (1), 1-2 (2), 2-3 (3), 5-1 (4), 1-0 (4), 4-3 (5), 2-0 (7), 5-4 (9).
| edge | same component? | action | total | taken |
|---|---|---|---|---|
| 4-1 (1) | no | take | 1 | 1 |
| 1-2 (2) | no | take | 3 | 2 |
| 2-3 (3) | no | take | 6 | 3 |
| 5-1 (4) | no | take | 10 | 4 |
| 1-0 (4) | no | take | 14 | 5 = V - 1, stop |
The edges 4-3, 2-0 and 5-4 are never examined. Had we continued, all three would have been rejected, since their endpoints are already connected. The answer is 14.
Arrays.sort on objects is stable, so 5-1 comes before 1-0 because it appears earlier in the input. Ties can change which MST is built, but never its weight.
Complexity. Time O(E log E), dominated by the sort. The DSU work is O(E · α(V)). Space O(V) for the DSU, plus O(E) for the sorted copy.
Kruskal or Prim?
Both are greedy and both are correct for the same reason, the cut property. Kruskal’s thinks in edges: sort once, then a nearly free check per edge. It is the natural choice when the input is an edge list, when the graph is sparse, or when you also want the components along the way (it builds a minimum spanning forest of a disconnected graph for free). Prim’s thinks in vertices: it grows one tree from a start vertex and suits adjacency lists and dense graphs, where the O(V²) array version wins.
Edge cases and pitfalls
- Sort a copy.
edges.clone()copies only the outer array, but that is enough here: sorting reorders the references without touching the input order. The inner arrays are never modified. - Comparator overflow.
(a, b) -> a[2] - b[2]can overflow when weights have large magnitudes of opposite sign.Integer.compareis always safe. - Disconnected graph. Fewer than
V - 1edges get taken, and the method returns-1. Without that check you would silently return the weight of a spanning forest. - Self-loops and parallel edges. A self-loop has both endpoints in the same set, so it is rejected automatically. Of two parallel edges, the lighter one is examined first and taken, and the heavier one is rejected.
- Negative weights. Kruskal’s works unchanged with negative weights, because it only compares them. The tests include a graph with weights
-2and-1. - Single vertex. Zero edges are needed, so the answer is
0. - Overflow of the total. With
w ≤ 1000the total fits easily in anint. Uselongif weights can be large.
Related problems
- MST theory proves the cut and cycle properties used above.
- Prim’s Algorithm solves the same problem by growing a single tree.
- Disjoint Set explains union by rank, union by size and path compression in detail.
- Number of operations to make network connected uses the same “skip edges inside a component” check to count spare cables.
- Swim in Rising Water can be solved by adding cells in increasing order with a DSU, Kruskal style.