Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Flows and Cuts

4.5.9 Global Minimum Cut (Stoer-Wagner)

4-Graphs/4.5.9_Global_Minimum_Cut_(Stoer-Wagner).cpp

Given a connected, undirected graph with nonnegative edge weights, the global minimum cut is the partition of the vertices into two nonempty sets that minimizes the total weight of edges crossing between them. Unlike a minimum s-t cut, no source or sink is fixed; the goal is the cheapest way to split the graph in two. The Stoer-Wagner algorithm finds it without any maximum flow computation by repeatedly running a "minimum cut phase".

A phase grows a set, starting from an arbitrary vertex, by repeatedly adding the most tightly connected outside vertex (the one with the greatest total weight to the set so far), exactly as in Prim's algorithm. The last vertex added, together with its connection weight, yields one candidate cut, the "cut of the phase". The last two vertices added are then merged into a single supernode and the next phase begins on the smaller graph. After $n - 1$ phases every candidate has been considered, and the smallest is the global minimum cut.

  • stoer_wagner(adj) returns a pair (weight, side) for a graph as a symmetric adjacency matrix adj (with 0 for the diagonal and absent edges), where weight is the total weight of the global minimum cut and side lists the vertices on one side of that cut. The graph must have at least two vertices.

Implementation

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>

std::pair<int64_t, std::vector<int>> stoer_wagner(std::vector<std::vector<int64_t>> adj) {
  int n = static_cast<int>(adj.size());
  assert(n >= 2);
  for (int i = 0; i < n; i++) {
    assert(static_cast<int>(adj[i].size()) == n);
    for (int j = i + 1; j < n; j++) {
      assert(adj[i][j] == adj[j][i]);
    }
  }
  std::pair<int64_t, std::vector<int>> best = {INT64_MAX, {}};
  std::vector<std::vector<int>> groups(n);
  std::vector<int64_t> weight(n);
  std::vector<bool> exists(n, true), added(n);
  for (int i = 0; i < n; i++) {
    groups[i] = {i};
  }
  for (int phase = 1; phase < n; phase++) {
    std::fill(weight.begin(), weight.end(), 0);
    std::fill(added.begin(), added.end(), false);
    int s = -1;
    for (int it = 0; it <= n - phase; it++) {
      int sel = -1;
      for (int i = 0; i < n; i++) {
        if (exists[i] && !added[i] && (sel == -1 || weight[i] > weight[sel])) {
          sel = i;
        }
      }
      if (it == n - phase) {
        if (weight[sel] < best.first) {
          best = {weight[sel], groups[sel]};
        }
        groups[s].insert(groups[s].end(), groups[sel].begin(), groups[sel].end());
        for (int i = 0; i < n; i++) {
          adj[s][i] += adj[sel][i];
          adj[i][s] = adj[s][i];
        }
        exists[sel] = false;
        break;
      }
      added[sel] = true;
      for (int i = 0; i < n; i++) {
        weight[i] += adj[sel][i];
      }
      s = sel;
    }
  }
  return best;
}

Example Usage

#include <cassert>
using namespace std;

int main() {
  // Triangle with edge weights 0-1: 2, 1-2: 3, 0-2: 1.
  // Cuts: {0}|{1,2} = 3, {1}|{0,2} = 5, {2}|{0,1} = 4. Minimum is 3.
  vector<vector<int64_t>> adj{
      {0, 2, 1},
      {2, 0, 3},
      {1, 3, 0},
  };
  auto [weight, side] = stoer_wagner(adj);
  assert(weight == 3);
  assert((side == vector<int>{1, 2}));  // The shore {1, 2}, opposite the single vertex 0.
  return 0;
}