Chapter 8. The Minimum Spanning Tree Problem
Classical sequential MST algorithms (Kruskal, Prim) and the union-find primitive.
This page: Classical forms. View LLP forms »
This page collects the classical / sequential implementations of the algorithms developed in this chapter. The lattice-linear (LLP) reformulations and chapter setup live on the LLP companion page.
Kruskal (sequential)
The classical edge-sorted scan with union-by-rank and path compression. $O(m \log n)$.
Time complexity: $O(m \log m)$ for the edge sort, $O(m \alpha(n))$ for the union-find work, where $n$ is the number of vertices and $m$ is the number of edges.
boolean[] mst(int n, int[] U, int[] V, int[] W) {
int m = U.length;
boolean[] inTree = new boolean[m];
int[] parent = new int[n];
int[] rank = new int[n];
forall i in [0..n-1] : parent[i] = i;
int chosen = 0;
int e = 0;
while (e < m && chosen < n - 1) {
int u = U[e];
int v = V[e];
int ru = root(parent, u);
int rv = root(parent, v);
if (ru != rv) {
inTree[e] = true;
chosen = chosen + 1;
if (rank[ru] < rank[rv]) { parent[ru] = rv; }
else if (rank[ru] > rank[rv]) { parent[rv] = ru; }
else { parent[rv] = ru; rank[ru] = rank[ru] + 1; }
};
e = e + 1;
};
return inTree;
}
int root(int[] parent, int x) {
if (parent[x] != x) { parent[x] = root(parent, parent[x]); };
return parent[x];
}
Prim (sequential)
Linear-scan Prim using a weight matrix; returns parent[], where
parent[v] is the predecessor of $v$ in the MST and is $-1$ for the root.
$O(n^2)$ — slower than the heap variant on sparse graphs but cache-friendly.
Time complexity: $O((n + m) \log n)$ with a binary heap, where $n$ is the number of vertices and $m$ is the number of edges.
int[] mst(int[][] w) {
int n = w.length;
int[] d = new int[n];
int[] parent = new int[n];
boolean[] fixed = new boolean[n];
forall i in [0..n-1] : { d[i] = 2147483647; parent[i] = -1; };
d[0] = 0;
int count = 0;
while (count < n) {
int v = -1;
int best = 2147483647;
int k = 0;
while (k < n) {
if (!fixed[k] && d[k] < best) { v = k; best = d[k]; };
k = k + 1;
};
if (v == -1) { return parent; };
fixed[v] = true;
count = count + 1;
k = 0;
while (k < n) {
if (!fixed[k] && w[v][k] != 2147483647 && w[v][k] < d[k]) {
d[k] = w[v][k];
parent[k] = v;
};
k = k + 1;
}
};
return parent;
}
UnionFind
The standard disjoint-set data structure with path compression in find
and union-by-rank in union. Both operations are amortised $O(\alpha(n))$
where $\alpha$ is the inverse Ackermann function.
Time complexity: $O(\alpha(n))$ amortised per find / union with path compression and union by rank — effectively constant for all practical $n$.
int find(int[] parent, int x) {
if (parent[x] != x) { parent[x] = find(parent, parent[x]); };
return parent[x];
}
boolean union(int[] parent, int[] rank, int x, int y) {
int rx = find(parent, x);
int ry = find(parent, y);
if (rx == ry) { return false; };
if (rank[rx] < rank[ry]) { parent[rx] = ry; }
else if (rank[rx] > rank[ry]) { parent[ry] = rx; }
else { parent[ry] = rx; rank[rx] = rank[rx] + 1; };
return true;
}