The cheapest way to split an undirected weighted graph into two non-empty parts, over all pairs. With unit weights it is the edge connectivity . Fixing and running max flows works but is slow; write Stoer–Wagner.
| Algorithm | Time | Notes |
|---|---|---|
| max flows from a fixed | maxflow | also the only simple option for directed graphs: , flows |
| Stoer–Wagner (Nagamochi–Ibaraki) | matrix, heap | deterministic, write this |
| Karger contraction | for high probability | the counting argument is the point |
| Karger–Stein | randomized recursion | |
| Karger (tree packing), Kawarabayashi–Thorup | near-linear | not for contests |
Stoer–Wagner
A maximum adjacency ordering adds, each step, the vertex with the largest total weight to the already-added set. The last vertex alone against everything else is a minimum – cut for the last two vertices ; record it, merge and , repeat.
pair<ll, vector<int>> stoerWagner(vector<vector<ll>> w) { // symmetric matrix; returns {cut, one side}
int n = sz(w); ll best = LLONG_MAX; vector<int> cut;
vector<vector<int>> group(n); for (int i = 0; i < n; i++) group[i] = {i};
for (int ph = 1; ph < n; ph++) {
vector<ll> key = w[0]; vector<char> added(n); int s = 0, t = 0;
for (int it = 0; it < n - ph; it++) { // vertex 0 always survives and starts the ordering
key[t] = LLONG_MIN; s = t;
t = max_element(all(key)) - key.begin();
for (int i = 0; i < n; i++) if (key[i] != LLONG_MIN) key[i] += w[t][i];
}
if (key[t] - w[t][t] < best) best = key[t] - w[t][t], cut = group[t];
group[s].insert(group[s].end(), all(group[t]));
for (int i = 0; i < n; i++) w[s][i] += w[t][i], w[i][s] = w[s][i];
w[0][t] = LLONG_MIN; // t is gone: never selected again
}
return {best, cut};
}The merged vertex is removed by setting w[0][t] to , which seeds key[t] with in later phases; adding finite weights never brings it back above real keys. With a heap and adjacency lists the same is .
Proof: the cut-of-the-phase lemma
Let be the MA ordering, , , . Take any cut separating and . Call active if and are on different sides of ; is active. For a vertex let be the weight of edges of inside . Claim: for every active .
For the first active , all of is on one side and on the other, so . If is active and is the next active vertex, then
.
The first inequality is the MA rule ( was preferred over when had been added); the second is induction; the third holds because the vertices of ( up to the vertex before ) all lie on ‘s side while is on the other side, so these edges are cut edges counted in but not in . With : .Global correctness: a global minimum cut either separates the current (then this phase’s cut is at most it) or keeps them together, and then it is a cut of the merged graph with the same weight. Induction on the number of vertices finishes.
Karger contraction and counting minimum cuts
Contract uniformly random edges (probability proportional to weight) until two super-vertices remain; the edges between them form a cut. A fixed minimum cut survives with probability at least , so runs succeed w.h.p.; a run is Kruskal on a random edge order.
Corollary: a graph has at most global minimum cuts (tight for a cycle), and at most cuts of weight .
Proof: survival probability and the bound
Let the minimum cut have weight . When super-vertices remain, every super-vertex has weighted degree (its star is a cut), so the total weight is and a random edge lies in the fixed cut with probability . Contraction never destroys a cut unless it contracts one of its edges, so survival has probability . The events “the run outputs cut ” are disjoint for distinct minimum cuts and each has probability , so there are at most of them. Repeating times fails with probability .
Karger–Stein
Contracting from to keeps a fixed minimum cut with probability : the danger is only at the end, so contract to twice independently and recurse on both. , success probability ; repeat times.
mt19937_64 krng(1234567);
ll kargerStein(vector<vector<ll>> w) { // one run; repeat and take the minimum
int n = sz(w);
if (n <= 6) {
ll best = LLONG_MAX;
for (int mask = 1; mask < (1 << n) - 1; mask++) {
ll c = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if ((mask >> i & 1) && !(mask >> j & 1)) c += w[i][j];
best = min(best, c);
}
return best;
}
auto contract = [&](vector<vector<ll>> a, int t) -> vector<vector<ll>> {
int m = sz(a); vector<ll> deg(m); vector<int> alive(m); iota(all(alive), 0);
for (int i = 0; i < m; i++) deg[i] = accumulate(all(a[i]), 0LL);
while (sz(alive) > t) {
ll tot = 0; for (int v : alive) tot += deg[v];
if (!tot) return {}; // disconnected: the answer is 0
ll r = uniform_int_distribution<ll>(0, tot - 1)(krng); int u = 0, v = 0;
for (int x : alive) { if (r < deg[x]) { u = x; break; } r -= deg[x]; }
r = uniform_int_distribution<ll>(0, deg[u] - 1)(krng);
for (int x : alive) { if (r < a[u][x]) { v = x; break; } r -= a[u][x]; }
deg[u] += deg[v] - 2 * a[u][v];
for (int x : alive) a[u][x] += a[v][x], a[x][u] = a[u][x];
a[u][u] = 0; alive.erase(find(all(alive), v));
}
vector<vector<ll>> b(t, vector<ll>(t));
for (int i = 0; i < t; i++) for (int j = 0; j < t; j++) b[i][j] = a[alive[i]][alive[j]];
return b;
};
int t = (int)ceil(1 + n / sqrt(2.0)); ll best = LLONG_MAX;
for (int rep = 0; rep < 2; rep++) {
auto b = contract(w, t);
if (b.empty()) return 0;
best = min(best, kargerStein(b));
}
return best;
}Proof: Karger–Stein succeeds with probability
Contracting from to keeps a fixed minimum cut with probability for . Let be a lower bound on the success probability at recursion height (leaves are exact, ). A branch succeeds with probability , and the two branches are independent, so . By induction : is increasing on , and because . The height is , so one run succeeds with probability , and the time recurrence solves to by the master theorem (two subproblems of size , work, ).
Sparse certificates (Nagamochi–Ibaraki)
To decide cuts smaller than , replace by the union of forests where is a maximal spanning forest of . It has at most edges and preserves every cut of size exactly. Greedy one pass: put each edge into the first forest whose DSU does not yet connect its endpoints; connectivity is monotone in , so binary search the forest. Use before -edge-connectivity checks, small max flows, or Stoer–Wagner on dense multigraphs. (Nagamochi–Ibaraki obtain the same forests in from one MA ordering.)
vector<int> sparseCertificate(int n, const vector<pair<int, int>>& E, int k) { // ids of the kept edges
vector<DSU> F(k, DSU(n)); vector<int> keep;
for (int i = 0; i < sz(E); i++) {
auto [u, v] = E[i]; int lo = 0, hi = k; // first forest not connecting u and v
while (lo < hi) { int mid = (lo + hi) / 2; if (F[mid].find(u) == F[mid].find(v)) lo = mid + 1; else hi = mid; }
if (lo < k) F[lo].unite(u, v), keep.push_back(i);
}
return keep;
}Proof: the certificate preserves all cuts below
Monotonicity: the components of always refine those of . An edge enters only when its endpoints are already connected in , so it merges two -components inside one -component; merges in only coarsen the coarser partition. So “connected in ” is monotone in and the binary search finds the first forest where are separate. Maximality: an edge skipped by had its endpoints connected in at that moment, so each is a maximal forest of the edges that reached it. Cuts: let be a cut with in . The forests are edge-disjoint, so some has no edge across the cut. Every discarded edge has connected in that , hence on the same side. So . Since always, for every cut.
Applications
Beyond raw edge connectivity: network reliability (the min cut is the bottleneck against random failure), clustering and community detection, image segmentation (min-cut partitioning of a pixel similarity graph), and circuit partitioning in VLSI design (minimising cross-partition wiring). Certificates (above) are maintained incrementally inside dynamic connectivity structures, and the same “sparsify before you compute” philosophy produced cut sparsifiers (Benczúr-Karger) and spectral sparsifiers (Spielman-Teng), preserving all cuts approximately within using edges.