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.
| Method | Time | Gives |
|---|---|---|
| Edmonds’ blossom (BFS + contraction) | the matching, comfortably | |
| Tutte matrix rank (random) | size; with Mucha–Sankowski elimination also the matching | |
| Micali–Vazirani | theory — notoriously hard to implement correctly | |
| greedy on trees / forests | match 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).
Proof: why contraction is safe, and why one pass over roots suffices
Lifting. A blossom is an odd cycle with matching edges ; its base is the only vertex not matched inside it and is reached from the root by an even alternating path (the stem). From with even, walking starts with the matched edge and has even length ; with odd, walking starts with the matched edge and has even length . So every blossom vertex has an even alternating path to , an augmenting path of through the contracted vertex lifts to one of by inserting that detour, and
markPathstores these detours inp.No path is lost. Suppose has an augmenting path. Flip the stem: the matching keeps its size, becomes free and is still an odd cycle whose only vertex not matched inside is . Take an augmenting path for this matching. If avoids , it is augmenting in . Otherwise, since is the only free vertex of , some endpoint of lies outside ; follow from to its first vertex in . The edge entering is unmatched (the matched edges of ‘s vertices other than stay inside , and is free), so this prefix is an augmenting path in ending at the free contracted vertex. Thus the flipped matching is not maximum in , and neither is the original one (same size), so by Berge has an augmenting path for it. Hence the search on the contracted graph finds an augmenting path from the root iff one exists in from the root’s tree.
One pass. Claim: if no augmenting path starts at for , none starts at for ( augmenting between and ). Let be -augmenting from ; if misses , it is -augmenting. Else let be the first vertex of on . The -edge entering is not on (its other end is not on ), and ‘s -edge is on , so the entering edge is unmatched in and in . If , is already -augmenting. Otherwise continue from along through ‘s -matched edge to the end of ; the result alternates, ends at an -free endpoint and is simple. Contradiction. So each vertex needs to be tried as a root once, and after the loop no free vertex starts an augmenting path: the matching is maximum by Berge.
Time. One search contracts at most blossoms at each and scans each edge times per endpoint: ; roots give .
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;
}Proof: Tutte–Berge and the decomposition
Upper bound: for any , every odd component of either has an unmatched vertex or sends a matching edge to ; at most can do the latter, so at least vertices are unmatched and .
Even vertices = : for a maximum matching , is missed by some maximum matching iff is reachable from a free vertex by an even alternating path (flip it; conversely gives such a path, as in the bipartite case). After the last search from a free root fails,
usedmarks exactly the vertices with an even alternating path from that root (blossom vertices have one by the lifting argument), so the union over free roots is .Equality (Gallai–Edmonds): take the final search forest from all free roots. Odd (inner) vertices are matched to even ones, and there is no even–even edge between different trees or blossoms (it would give an augmenting path) and no edge from to (it would make the vertex odd, i.e. in ). So the components of are the contracted blossoms and single even vertices, each odd and factor-critical (a blossom minus any vertex has a perfect matching by rotating around the cycle), the vertices are the odd tree vertices each matched into a different component, and is perfectly matched within itself. Counting with : the unmatched vertices are exactly one per component not matched to , i.e. , which meets the upper bound.
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;
}
}Proof: rank of the Tutte matrix
Perfect matchings. If has a perfect matching , substituting on and elsewhere makes block-diagonal with blocks \begin{psmallmatrix}0&1\\-1&0\end{psmallmatrix}, determinant , so . Conversely expand . Terms of permutations with an odd cycle cancel: reversing the odd cycle containing the smallest such vertex keeps the cycle type (so the sign) and flips an odd number of entries (fixed points have anyway). So if some permutation with only even cycles uses edges of everywhere, and alternate edges of each cycle form a perfect matching.
Rank . For a skew-symmetric , if the rows are a basis of the row space then the principal submatrix is nonsingular: write the other rows as ; skew-symmetry gives , so a vector with would give , contradicting independence. Hence the rank is the largest nonsingular principal submatrix; a principal submatrix on is the Tutte matrix of , nonsingular iff has a perfect matching, and the largest such has vertices. Random values: the relevant minor is a nonzero polynomial of degree , so by Schwartz–Zippel it vanishes at a random point mod with probability .
Extraction. After padding, a perfect matching exists (new vertices are joined to everything) and is invertible w.h.p. Jacobi’s complementary-minor identity gives , and the left side is since is skew-symmetric. So iff the Tutte matrix of is nonsingular, i.e. (w.h.p.) iff has a perfect matching; an edge with this property can be taken. The inverse of the remaining matrix is the Schur complement of the block in , which the two pivot steps compute in ; rounds give .
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).