Blossom algorithm
The Blossom algorithm, also called the blossom tree algorithm, solves the maximum-cardinality matching problem in general graphs. It was proposed by Jack Edmonds in 1961. With some modifications, it can also solve the maximum-weight matching problem. It was the first algorithm to prove that maximum matching has polynomial complexity.
Unlike bipartite matching, matching in a general graph may involve odd cycles.

For this graph, directly taking the complement (exchanging matched and unmatched edges) can make the resulting invalid: some vertices may belong to two matched edges. The problem is caused by the odd cycle.
Now consider an augmenting algorithm for general graphs. From the perspective of bipartite graphs, enumerate an unmatched vertex each time, make it the root, mark it “o”, and then mark vertices alternately “o” and “i”. It is easy to see that an edge from “i” to “o” is a matched edge.
Suppose the current vertex is and an adjacent vertex is . There are two cases:
- has not been visited. If is unmatched, an augmenting path has been found; otherwise, continue searching from the mate of .
- has already been visited. Encountering a vertex marked “o” means that we need to shrink a blossom; otherwise, we have encountered an even cycle and skip it.
An even cycle can be handled as in a bipartite graph, so it may be ignored. After shrinking the blossom, continue looking for an augmenting path in the new graph.

Let the original graph be and the graph after shrinking the blossom be . We only need to prove:
- If has an augmenting path, then also has one.
- If has an augmenting path, then also has one.

Let the non-tree edge (the edge that forms the cycle) be , and define the blossom base as . An odd cycle is alternating, and the two edges incident to have the same type—both are unmatched—if and only if this is the case. Therefore, the tree edge entering must be matched, and every vertex other than on the cycle has an unmatched edge leading outside the cycle.
There are two ways to leave the cycle through an outside edge: clockwise and counterclockwise.

Thus, shrinking or not shrinking the blossom does not affect correctness.
In an implementation, after finding a blossom we do not need to actually shrink it. We can use an array to record the blossom rooted at which vertex contains each vertex.
Complexity analysis
For each augmenting-path search, traverse all edges. When a blossom is encountered, maintaining its vertices costs .
Enumerating every unmatched vertex as the start of an augmenting-path search gives a total complexity of .
Reference code
Reference code
// graph
template <typename T>
class graph {
public:
struct edge {
int from;
int to;
T cost;
};
vector<edge> edges;
vector<vector<int>> g;
int n;
graph(int _n) : n(_n) { g.resize(n); }
virtual int add(int from, int to, T cost) = 0;
};
// undirectedgraph
template <typename T>
class undirectedgraph : public graph<T> {
public:
using graph<T>::edges;
using graph<T>::g;
using graph<T>::n;
undirectedgraph(int _n) : graph<T>(_n) {}
int add(int from, int to, T cost = 1) {
assert(0 <= from && from < n && 0 <= to && to < n);
int id = (int)edges.size();
g[from].push_back(id);
g[to].push_back(id);
edges.push_back({from, to, cost});
return id;
}
};
// blossom / find_max_unweighted_matching
template <typename T>
vector<int> find_max_unweighted_matching(const undirectedgraph<T> &g) {
std::mt19937 rng(std::random_device{}());
vector<int> match(g.n, -1); // matching
vector<int> aux(g.n, -1); // timestamp
vector<int> label(g.n); // "o" or "i"
vector<int> orig(g.n); // blossom base
vector<int> parent(g.n, -1); // parent
queue<int> q;
int aux_time = -1;
auto lca = [&](int v, int u) {
aux_time++;
while (true) {
if (v != -1) {
if (aux[v] == aux_time) { // found a visited vertex, i.e. the LCA
return v;
}
aux[v] = aux_time;
if (match[v] == -1) {
v = -1;
} else {
v = orig[parent[match[v]]]; // continue from the parent of the matched vertex
}
}
swap(v, u);
}
}; // lca
auto blossom = [&](int v, int u, int a) {
while (orig[v] != a) {
parent[v] = u;
u = match[v];
if (label[u] == 1) { // mark the root as "o" and find an augmenting path
label[u] = 0;
q.push(u);
}
orig[v] = orig[u] = a; // shrink the blossom
v = parent[u];
}
}; // blossom
auto augment = [&](int v) {
while (v != -1) {
int pv = parent[v];
int next_v = match[pv];
match[v] = pv;
match[pv] = v;
v = next_v;
}
}; // augment
auto bfs = [&](int root) {
fill(label.begin(), label.end(), -1);
iota(orig.begin(), orig.end(), 0);
while (!q.empty()) {
q.pop();
}
q.push(root);
// mark the root as "o"; use "0" for "o" and "1" for "i"
label[root] = 0;
while (!q.empty()) {
int v = q.front();
q.pop();
for (int id : g.g[v]) {
auto &e = g.edges[id];
int u = e.from ^ e.to ^ v;
if (label[u] == -1) { // found an unvisited vertex
label[u] = 1; // mark as "i"
parent[u] = v;
if (match[u] == -1) { // found an unmatched vertex
augment(u); // find an augmenting path
return true;
}
// found a matched vertex; put its mate in the queue to extend the alternating tree
label[match[u]] = 0;
q.push(match[u]);
continue;
} else if (label[u] == 0 && orig[v] != orig[u]) {
// found a visited vertex with the same "o" label: a blossom
int a = lca(orig[v], orig[u]);
// find the LCA, then shrink the blossom
blossom(u, v, a);
blossom(v, u, a);
}
}
}
return false;
}; // bfs
auto greedy = [&]() {
vector<int> order(g.n);
// randomly shuffle order
iota(order.begin(), order.end(), 0);
shuffle(order.begin(), order.end(), rng);
// match vertices whenever possible
for (int i : order) {
if (match[i] == -1) {
for (auto id : g.g[i]) {
auto &e = g.edges[id];
int to = e.from ^ e.to ^ i;
if (match[to] == -1) {
match[i] = to;
match[to] = i;
break;
}
}
}
}
}; // greedy
// make a random matching first
greedy();
// find augmenting paths from unmatched vertices
for (int i = 0; i < g.n; i++) {
if (match[i] == -1) {
bfs(i);
}
}
return match;
}
UOJ #79. General graph maximum matching
#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <queue>
#include <random>
#include <vector>
using namespace std;
// graph
template <typename T>
class graph {
public:
struct edge {
int from;
int to;
T cost;
};
vector<edge> edges;
vector<vector<int>> g;
int n;
graph(int _n) : n(_n) { g.resize(n); }
virtual int add(int from, int to, T cost) = 0;
};
// undirectedgraph
template <typename T>
class undirectedgraph : public graph<T> {
public:
using graph<T>::edges;
using graph<T>::g;
using graph<T>::n;
undirectedgraph(int _n) : graph<T>(_n) {}
int add(int from, int to, T cost = 1) {
assert(0 <= from && from < n && 0 <= to && to < n);
int id = (int)edges.size();
g[from].push_back(id);
g[to].push_back(id);
edges.push_back({from, to, cost});
return id;
}
};
// blossom / find_max_unweighted_matching
template <typename T>
vector<int> find_max_unweighted_matching(const undirectedgraph<T> &g) {
std::mt19937 rng(114514); // the random seed is irrelevant here
// chrono::steady_clock::now().time_since_epoch().count() can also be used
// to obtain the current time
vector<int> match(g.n, -1); // matching
vector<int> aux(g.n, -1); // timestamp
vector<int> label(g.n); // "o" or "i"
vector<int> orig(g.n); // blossom base
vector<int> parent(g.n, -1); // parent
queue<int> q;
int aux_time = -1;
auto lca = [&](int v, int u) {
aux_time++;
while (true) {
if (v != -1) {
if (aux[v] == aux_time) { // found a visited vertex, i.e. the LCA
return v;
}
aux[v] = aux_time;
if (match[v] == -1) {
v = -1;
} else {
v = orig[parent[match[v]]]; // continue from the parent of the matched vertex
}
}
swap(v, u);
}
}; // lca
auto blossom = [&](int v, int u, int a) {
while (orig[v] != a) {
parent[v] = u;
u = match[v];
if (label[u] == 1) { // mark the root as "o" and find an augmenting path
label[u] = 0;
q.push(u);
}
orig[v] = orig[u] = a; // shrink the blossom
v = parent[u];
}
}; // blossom
auto augment = [&](int v) {
while (v != -1) {
int pv = parent[v];
int next_v = match[pv];
match[v] = pv;
match[pv] = v;
v = next_v;
}
}; // augment
auto bfs = [&](int root) {
fill(label.begin(), label.end(), -1);
iota(orig.begin(), orig.end(), 0);
while (!q.empty()) {
q.pop();
}
q.push(root);
// mark the root as "o"; use "0" for "o" and "1" for "i"
label[root] = 0;
while (!q.empty()) {
int v = q.front();
q.pop();
for (int id : g.g[v]) {
auto &e = g.edges[id];
int u = e.from ^ e.to ^ v;
if (label[u] == -1) { // found an unvisited vertex
label[u] = 1; // mark as "i"
parent[u] = v;
if (match[u] == -1) { // found an unmatched vertex
augment(u); // find an augmenting path
return true;
}
// found a matched vertex; put its mate in the queue to extend the alternating tree
label[match[u]] = 0;
q.push(match[u]);
continue;
} else if (label[u] == 0 && orig[v] != orig[u]) {
// found a visited vertex with the same "o" label: a "blossom"
int a = lca(orig[v], orig[u]);
// find the LCA, then shrink the blossom
blossom(u, v, a);
blossom(v, u, a);
}
}
}
return false;
}; // bfs
auto greedy = [&]() {
vector<int> order(g.n);
// randomly shuffle order
iota(order.begin(), order.end(), 0);
shuffle(order.begin(), order.end(), rng);
// match vertices whenever possible
for (int i : order) {
if (match[i] == -1) {
for (auto id : g.g[i]) {
auto &e = g.edges[id];
int to = e.from ^ e.to ^ i;
if (match[to] == -1) {
match[i] = to;
match[to] = i;
break;
}
}
}
}
}; // greedy
// make a random matching first
greedy();
// find augmenting paths from unmatched vertices
for (int i = 0; i < g.n; i++) {
if (match[i] == -1) {
bfs(i);
}
}
return match;
}
int main() {
ios::sync_with_stdio(false);
int n, m;
cin >> n >> m;
undirectedgraph<int> g(n);
while (m--) {
int u, v;
cin >> u >> v;
g.add(u - 1, v - 1); // 0-based
}
auto match = find_max_unweighted_matching(g);
cout << count_if(match.begin(), match.end(), [](int x) { return x != -1; }) /
2
<< endl;
for (int i = 0; i < n; i++) cout << match[i] + 1 << " \n"[i == n - 1];
return 0;
}
General-graph matching based on Gaussian elimination
Tip
Before reading the following material, you may need to read the sections on matrices in [linear algebra]:
This section introduces a general-graph matching algorithm based on Gaussian elimination. Compared with the traditional blossom algorithm, it is easier to understand and implement, and is convenient for problems such as finding vertices that must occur in a maximum matching. Its disadvantage is a larger constant factor: Gaussian elimination essentially runs at its full complexity, whereas the blossom algorithm usually does not.
Prerequisite: the Tutte matrix
Definition: For an undirected graph with vertices, its Tutte matrix is an matrix defined by:
Here is a variable, so contains variables.
When there is no ambiguity, we abbreviate as below.
Theorem (Tutte's theorem): has a perfect matching if and only if .
Proof
Introduce the notion of an even-cycle cover: an even-cycle cover of an undirected graph is a collection of even cycles (including 2-cycles) that covers every vertex exactly once.
It is easy to prove that has a perfect matching if and only if has an even-cycle cover.
- If has an even-cycle cover, take every other edge on each cycle to obtain a perfect matching.
- If has a perfect matching, turn each matched edge into a 2-cycle to obtain an even-cycle cover.
We now prove that has an even-cycle cover if and only if .
Consider the definition of the determinant:
Here is an arbitrary permutation, and is when has an odd number of inversions and otherwise.
It is not difficult to see that every permutation can be viewed as an even-cycle cover of . If this cover contains an odd cycle, the sum of the term and the term obtained by reversing that cycle is . Therefore, only even-cycle covers can make the determinant different from . This proves the theorem.
Theorem: is always even, and the size of a maximum matching in equals half of .
Proof
The rank of a skew-symmetric matrix is even; the second statement is left to the reader.
In practice, it is impossible to compute with variables. Instead, choose a field, such as the residue field of a prime , randomly replace the variables with values in , and then compute. For convenience, when there is no ambiguity, below refers directly to the resulting matrix.
Theorem: is at most twice the size of a maximum matching in , and the two are equal with probability at least .
Since in general-graph maximum matching is usually no greater than , a prime on the order of is sufficient in practice.
The theorem shows that if we only need the size of a maximum matching and not the matching itself, a single Gaussian elimination to obtain is enough, which is much simpler than the blossom algorithm. If we need to output the matching, the problem is slightly more complicated and uses the algorithm below.
Constructing a perfect matching
By Tutte's theorem and the theorem above, if has a perfect matching, then is full rank with high probability. For convenience, the following discussion omits “with high probability.”
Let denote the vertex numbered in . We have the following theorem:
Theorem: has a perfect matching.
Inverse and adjugate matrices
For any -order square matrix , define its adjugate matrix by , where is the minor obtained by deleting row and column . Equivalently, if is the matrix of cofactors of , then .
Theorem: If is invertible, then .
Therefore, , which means that the submatrix left after deleting row and column of has full rank.
In other words, if and , then there is a perfect matching containing the edge . We call such an edge feasible below.
The theorem gives an obvious brute-force algorithm for finding a perfect matching in an undirected graph that has one: enumerate each time; if is a feasible edge (the edge exists and ), add to the matching, delete both vertices from , and recompute the new .
There are rounds, each costing , for a total complexity of , which is somewhat slow. When recomputing , however, we need not calculate the inverse from scratch with Gaussian elimination each time; we can use the following theorem:
Theorem (elimination theorem): Let
and suppose . Then
The theorem describes eliminating the first row and first column. It clearly generalizes to eliminating any row and column, so we only need to compute once at the start; deleting two vertices thereafter requires only two elimination operations.
The description is somewhat abstract; see the C++ code
void eliminate(int A[][MAXN], int r, int c) { // eliminate row r and column c
row_marked[r] = col_marked[c] = true; // already eliminated
int inv = quick_power(A[r][c], p - 2); // modular inverse
for (int i = 1; i <= n; i++)
if (!row_marked[i] && A[i][c]) {
int tmp = (long long)A[i][c] * inv % p;
for (int j = 1; j <= n; j++)
if (!col_marked[j] && A[r][j])
A[i][j] = (A[i][j] - (long long)tmp * A[r][j]) % p;
}
}
There are rounds, each with complexity , so the above algorithm finds a perfect matching in time.
Constructing a maximum matching
We have just solved the problem of constructing a perfect matching, but most problems ask for a maximum matching.
As mentioned above, the size of a maximum matching in equals half of . If we can find a maximum-rank nonsingular submatrix of , then finding a perfect matching in the subgraph induced by that submatrix gives a maximum matching of .
Equivalently, if has a perfect matching, then is full rank; in other words, has linearly independent rows. If is not full rank, we can find a basis of 's rows and retain the corresponding rows and columns to obtain a maximum-rank nonsingular submatrix of .
After finding the maximum-rank nonsingular submatrix, use the algorithm above to find a perfect matching in the induced subgraph, obtaining a maximum matching of the original graph. Since Gaussian elimination may exchange rows, take care to maintain the vertex numbering during implementation.
UOJ #79. General graph maximum matching
#include <cstring>
#include <iostream>
#include <random>
#include <utility>
using namespace std;
constexpr int MAXN = 505, p = (int)1e9 + 7;
int qpow(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (long long)ans * a % p;
a = (long long)a * a % p;
b >>= 1;
}
return ans;
}
int A[MAXN][MAXN], B[MAXN][MAXN], t[MAXN][MAXN], id[MAXN];
// Gaussian elimination, O(n^3)
// pass B to compute the inverse; pass nullptr to compute only the matrix rank
void Gauss(int A[][MAXN], int B[][MAXN], int n) {
if (B) {
memset(B, 0, sizeof(t));
for (int i = 1; i <= n; i++) B[i][i] = 1;
}
for (int i = 1; i <= n; i++) {
if (!A[i][i]) {
for (int j = i + 1; j <= n; j++)
if (A[j][i]) {
swap(id[i], id[j]);
for (int k = i; k <= n; k++) swap(A[i][k], A[j][k]);
if (B)
for (int k = 1; k <= n; k++) swap(B[i][k], B[j][k]);
break;
}
if (!A[i][i]) continue;
}
int inv = qpow(A[i][i], p - 2);
for (int j = 1; j <= n; j++)
if (i != j && A[j][i]) {
int t = (long long)A[j][i] * inv % p;
for (int k = i; k <= n; k++)
if (A[i][k]) A[j][k] = (A[j][k] - (long long)t * A[i][k]) % p;
if (B) {
for (int k = 1; k <= n; k++)
if (B[i][k]) B[j][k] = (B[j][k] - (long long)t * B[i][k]) % p;
}
}
}
if (B)
for (int i = 1; i <= n; i++) {
int inv = qpow(A[i][i], p - 2);
for (int j = 1; j <= n; j++)
if (B[i][j]) B[i][j] = (long long)B[i][j] * inv % p;
}
}
bool row_marked[MAXN] = {false}, col_marked[MAXN] = {false};
int sub_n; // size of the maximum-rank nonsingular submatrix
// eliminate one row and one column, O(n^2)
void eliminate(int r, int c) {
row_marked[r] = col_marked[c] = true; // already eliminated
int inv = qpow(B[r][c], p - 2);
for (int i = 1; i <= sub_n; i++)
if (!row_marked[i] && B[i][c]) {
int t = (long long)B[i][c] * inv % p;
for (int j = 1; j <= sub_n; j++)
if (!col_marked[j] && B[r][j])
B[i][j] = (B[i][j] - (long long)t * B[r][j]) % p;
}
}
int vertices[MAXN], girl[MAXN]; // girl is the matched vertex, used to output the matching
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
auto rng = mt19937(random_device{}());
int n, m;
cin >> n >> m; // number of vertices and edges
while (m--) {
int x, y;
cin >> x >> y;
A[x][y] = rng() % p;
A[y][x] = -A[x][y]; // Tutte matrix
}
for (int i = 1; i <= n; i++)
id[i] = i; // used to output the matching because Gaussian elimination may exchange columns
memcpy(t, A, sizeof(t));
Gauss(A, nullptr, n);
for (int i = 1; i <= n; i++)
if (A[id[i]][id[i]]) vertices[++sub_n] = i; // find a maximum-rank nonsingular submatrix
for (int i = 1; i <= sub_n; i++)
for (int j = 1; j <= sub_n; j++) A[i][j] = t[vertices[i]][vertices[j]];
Gauss(A, B, sub_n);
for (int i = 1; i <= sub_n; i++)
if (!girl[vertices[i]])
for (int j = i + 1; j <= sub_n; j++)
if (!girl[vertices[j]] && t[vertices[i]][vertices[j]] && B[j][i]) {
// note the if statement above: t is now a copy of the adjacency matrix,
// the inverse at row j, column i is nonzero if and only if this edge is feasible
girl[vertices[i]] = vertices[j];
girl[vertices[j]] = vertices[i];
eliminate(i, j);
eliminate(j, i);
break;
}
cout << sub_n / 2 << '\n';
for (int i = 1; i <= n; i++) cout << girl[i] << ' ';
return 0;
}
Exercises
References
- Mucha M, Sankowski P. Maximum matchings via Gaussian elimination
- Zhou Zixin, Yang Jiaqi, General Graph Matching Based on Linear Algebra
- ZYQN, General Graph Matching Algorithm Based on Linear Algebra