Maximum matching in a non-bipartite graph. Berge’s theorem still holds (Bipartite Matching), but an odd cycle lets the alternating search reach a vertex with both parities, so the plain DFS is wrong. Edmonds: contract such odd cycles (blossoms) and keep searching. First check whether the graph is secretly bipartite; if only the size is needed, the Tutte matrix rank is 15 lines.

MethodTimeGives
Edmonds’ blossom (BFS + contraction)the matching, comfortably
Tutte matrix rank (random)size; with Mucha–Sankowski elimination also the matching
Micali–Vaziranitheory — notoriously hard to implement correctly
greedy on trees / forestsmatch a leaf to its parent
Gabow scaling (weighted)theoretical
Blossom V (Kolmogorov)the standard weighted implementation used in practice — see Weighted General Matching

Checklist before reaching for any of this: (1) is the graph secretly bipartite — run a 2-colouring first, many “general” graphs in problems are bipartite by construction; (2) only existence or the size needed — Tutte matrix; (3) the actual matching, unweighted — blossom; (4) weighted — Blossom V, or model it as min-cost flow if step 1 found it’s actually bipartite.

Edmonds’ blossom algorithm

struct Blossom {
    int n; vector<vector<int>> g; vector<int> mt, p, base, q; vector<char> used, bl;
    Blossom(int n) : n(n), g(n), mt(n, -1), p(n), base(n), q(n), used(n), bl(n) {}
    void addEdge(int u, int v) { if (u != v) g[u].push_back(v), g[v].push_back(u); }
    int lca(int a, int b) {
        vector<char> seen(n);
        for (;;) { a = base[a]; seen[a] = 1; if (mt[a] < 0) break; a = p[mt[a]]; }
        for (;;) { b = base[b]; if (seen[b]) return b; b = p[mt[b]]; }
    }
    void markPath(int v, int b, int ch) {
        while (base[v] != b) { bl[base[v]] = bl[base[mt[v]]] = 1; p[v] = ch; ch = mt[v]; v = p[mt[v]]; }
    }
    int findPath(int root) {                                   // free vertex at the end of an augmenting path, or -1
        fill(all(used), 0); fill(all(p), -1); iota(all(base), 0);
        int qh = 0, qt = 0; used[root] = 1; q[qt++] = root;
        while (qh < qt) {
            int v = q[qh++];
            for (int to : g[v]) {
                if (base[v] == base[to] || mt[v] == to) continue;
                if (to == root || (mt[to] >= 0 && p[mt[to]] >= 0)) {      // even-even edge: a blossom
                    int cb = lca(v, to); fill(all(bl), 0);
                    markPath(v, cb, to); markPath(to, cb, v);
                    for (int i = 0; i < n; i++) if (bl[base[i]]) { base[i] = cb; if (!used[i]) used[i] = 1, q[qt++] = i; }
                } else if (p[to] < 0) {                                    // grow the tree
                    p[to] = v;
                    if (mt[to] < 0) return to;
                    used[mt[to]] = 1; q[qt++] = mt[to];
                }
            }
        }
        return -1;
    }
    int run() {
        int res = 0;
        for (int v = 0; v < n; v++) if (mt[v] < 0) for (int to : g[v]) if (mt[to] < 0) { mt[v] = to, mt[to] = v, res++; break; }
        for (int v = 0; v < n; v++) if (mt[v] < 0) {
            int u = findPath(v); if (u < 0) continue;
            res++;
            while (u >= 0) { int pv = p[u], ppv = mt[pv]; mt[u] = pv, mt[pv] = u, u = ppv; }
        }
        return res;
    }
};

used marks the even (outer) vertices of the search tree, including every vertex absorbed into a blossom; base maps each vertex to its current blossom base; p stores the tree edge into each odd vertex (rewired inside blossoms so that the path can be lifted).

Theory

  • Tutte–Berge formula: , where odd counts odd components. Tutte’s theorem: a perfect matching exists iff for all .
  • Gallai–Edmonds decomposition: = vertices missed by some maximum matching, = neighbours of outside , = the rest. Every maximum matching matches into distinct components of , each component of is factor-critical (deleting any vertex leaves a perfect matching), and has a perfect matching. attains Tutte–Berge.
  • Petersen: every bridgeless cubic graph has a perfect matching. Factor-critical graphs are exactly those with an odd ear decomposition.
  • Gallai’s (max matching + min edge cover) holds in all graphs; vertex cover and independent set are NP-hard outside bipartite graphs.
vector<int> gallaiEdmonds(Blossom& B) {                 // after B.run(): 0 = D, 1 = A, 2 = C
    int n = B.n; vector<int> type(n, 2);
    for (int r = 0; r < n; r++) if (B.mt[r] < 0) { B.findPath(r); for (int v = 0; v < n; v++) if (B.used[v]) type[v] = 0; }
    for (int v = 0; v < n; v++) if (type[v] == 0) for (int u : B.g[v]) if (type[u] == 2) type[u] = 1;
    return type;
}

Tutte matrix (randomised)

Put a random at and at for each edge, modulo a large prime. Then with probability . To extract a matching, pad with extra vertices joined to everything until the matrix is invertible, invert it, and repeatedly pick an edge with , eliminating rows and columns (Rabin–Vazirani / Mucha–Sankowski elimination).

namespace tutte {
const ll MOD = 1'000'000'007; mt19937_64 rng(98765);
ll pw(ll a, ll e) { ll r = 1; for (a %= MOD; e; e >>= 1, a = a * a % MOD) if (e & 1) r = r * a % MOD; return r; }
int gauss(vector<vector<ll>>& m) {                           // reduced row echelon form, returns the rank
    int R = sz(m), C = R ? sz(m[0]) : 0, rk = 0;
    for (int c = 0; c < C && rk < R; c++) {
        int row = -1; for (int j = rk; j < R; j++) if (m[j][c]) { row = j; break; }
        if (row < 0) continue;
        swap(m[row], m[rk]); ll inv = pw(m[rk][c], MOD - 2);
        for (int k = c; k < C; k++) m[rk][k] = m[rk][k] * inv % MOD;
        for (int j = 0; j < R; j++) if (j != rk && m[j][c]) { ll v = m[j][c]; for (int k = c; k < C; k++) m[j][k] = (m[j][k] + MOD - v * m[rk][k] % MOD) % MOD; }
        rk++;
    }
    return rk;
}
vector<vector<ll>> build(int n, const vector<pair<int, int>>& E) {
    vector<vector<ll>> m(n, vector<ll>(n));
    for (auto [a, b] : E) if (a != b) { ll r = rng() % (MOD - 1) + 1; m[a][b] = r, m[b][a] = MOD - r; }
    return m;
}
int matchingSize(int n, const vector<pair<int, int>>& E) { auto m = build(n, E); return gauss(m) / 2; }
vector<pair<int, int>> matching(int n, const vector<pair<int, int>>& E) {
    auto mat = build(n, E), tmp = mat; int r = gauss(tmp), M = 2 * n - r;
    for (auto& row : mat) row.resize(M);
    mat.resize(M, vector<ll>(M));
    vector<vector<ll>> A;
    while (A.empty()) {                                      // join M - n new vertices to all old ones
        for (int i = 0; i < n; i++) for (int j = n; j < M; j++) { ll x = rng() % (MOD - 1) + 1; mat[i][j] = x, mat[j][i] = MOD - x; }
        vector<vector<ll>> x(M, vector<ll>(2 * M));
        for (int i = 0; i < M; i++) { x[i][i + M] = 1; for (int j = 0; j < M; j++) x[i][j] = mat[i][j]; }
        if (gauss(x) == M) { A.assign(M, vector<ll>(M)); for (int i = 0; i < M; i++) for (int j = 0; j < M; j++) A[i][j] = x[i][j + M]; }
    }
    vector<char> has(M, 1); vector<pair<int, int>> ret;
    for (int it = 0; it < M / 2; it++) {
        int fi = -1, fj = -1;
        for (int i = 0; i < M && fi < 0; i++) if (has[i]) for (int j = i + 1; j < M; j++) if (has[j] && A[i][j] && mat[i][j]) { fi = i, fj = j; break; }
        if (fi < 0) break;                                   // probability about n / MOD
        has[fi] = has[fj] = 0;
        if (fj < n) ret.push_back({fi, fj});
        for (int sw = 0; sw < 2; sw++) {
            ll a = pw(A[fi][fj], MOD - 2);
            for (int i = 0; i < M; i++) if (has[i] && A[i][fj]) { ll b = A[i][fj] * a % MOD; for (int j = 0; j < M; j++) A[i][j] = (A[i][j] + MOD - A[fi][j] * b % MOD) % MOD; }
            swap(fi, fj);
        }
    }
    return ret;
}
}

Patterns

  • Pair up items with compatibility on a general graph: blossom; “maximum number of disjoint pairs” questions.
  • Trees and forests: greedily match a deepest leaf with its parent (, optimal by an exchange argument; the same works for maximum matching on cacti with a DP).
  • Edge cover / independent pairs: when there are no isolated vertices.
  • Weighted: Weighted General Matching; with few vertices () a bitmask DP (pair the lowest unpaired vertex).
  • Graph games: in the undirected vertex geography game (players alternately extend a path from a token), the first player wins iff the start vertex is in every maximum matching, i.e. (Gallai–Edmonds).