Graphs / Matching and Assignment
4.6.4 Maximum Graph Matching (Blossom)
Given an undirected, unweighted simple graph, determine a maximum matching: a maximum subset of its edges such that no node is shared between different edges in the resulting subset.
Edmonds' blossom algorithm extends augmenting-path search to general (non-bipartite) graphs, where odd-length cycles called blossoms can conceal augmenting paths. Each blossom is contracted into a single node so the search can proceed, and is expanded afterward to recover the matching. For maximum-weight matching in bipartite graphs, use Hungarian or min-cost max-flow; for maximum-weight matching in general graphs, use the weighted blossom algorithm in the next section.
The graph must be simple: self-loops and parallel edges are not supported.
max_matching(adj)returns a matching for a bidirectionally pre-populated adjacency listadj, whose indices represent the nodes. The returned vectormatchhasmatch[u] == vandmatch[v] == uwhenuandvare matched, ormatch[u]is $-1$ whenuis unmatched.
Implementation
#include <algorithm>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>
std::vector<int> max_matching(const std::vector<std::vector<int>> &adj) {
int n = static_cast<int>(adj.size());
std::vector<int> match(n, -1), label(n), parent(n), base(n), aux(n, -1);
std::queue<int> q;
int aux_time = -1;
auto lca = [&](int u, int v) {
aux_time++;
while (true) {
if (u != -1) {
u = base[u];
if (aux[u] == aux_time) {
return u;
}
aux[u] = aux_time;
u = (match[u] == -1) ? -1 : parent[match[u]];
}
std::swap(u, v);
}
};
auto mark_blossom = [&](int u, int v, int b) {
while (base[u] != b) {
parent[u] = v;
v = match[u];
if (label[v] == 1) {
label[v] = 0;
q.push(v);
}
base[u] = base[v] = b;
u = parent[v];
}
};
auto augment = [&](int u) {
while (u != -1) {
int p = parent[u];
int next = match[p];
match[u] = p;
match[p] = u;
u = next;
}
};
auto find_path = [&](int root) {
label.assign(n, -1);
parent.assign(n, -1);
std::iota(base.begin(), base.end(), 0);
while (!q.empty()) {
q.pop();
}
label[root] = 0;
q.push(root);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : adj[u]) {
if (base[u] == base[v] || match[u] == v) {
continue;
}
if (label[v] == -1) {
label[v] = 1;
parent[v] = u;
if (match[v] == -1) {
augment(v);
return true;
}
label[match[v]] = 0;
q.push(match[v]);
} else if (label[v] == 0) {
int b = lca(u, v);
mark_blossom(u, v, b);
mark_blossom(v, u, b);
}
}
}
return false;
};
for (int i = 0; i < n; i++) {
if (match[i] == -1) {
for (int v : adj[i]) {
if (i != v && match[v] == -1) {
match[i] = v;
match[v] = i;
break;
}
}
}
}
for (int i = 0; i < n; i++) {
if (match[i] == -1) {
find_path(i);
}
}
return match;
}
Example Usage
#include <cassert>
using namespace std;
vector<vector<int>> adj;
void add_edge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
int matching_size(const vector<int> &match) {
return count_if(match.begin(), match.end(), [](int v) { return v != -1; }) / 2;
}
int main() {
{
// 0---1
// | |
// 3---2
int nodes = 4;
adj.assign(nodes, {});
add_edge(0, 1);
add_edge(1, 2);
add_edge(2, 3);
add_edge(3, 0);
vector<int> match = max_matching(adj);
assert(matching_size(match) == 2);
assert((match == vector<int>{1, 0, 3, 2}));
}
{
// 3---0---1---4
// \ /
// 2
int nodes = 5;
adj.assign(nodes, {});
add_edge(0, 1);
add_edge(1, 2);
add_edge(2, 0);
add_edge(0, 3);
add_edge(1, 4);
assert(matching_size(max_matching(adj)) == 2);
}
return 0;
}
/*
Given an undirected, unweighted simple graph, determine a maximum matching: a maximum subset of its
edges such that no node is shared between different edges in the resulting subset.
Edmonds' blossom algorithm extends augmenting-path search to general (non-bipartite) graphs, where
odd-length cycles called blossoms can conceal augmenting paths. Each blossom is contracted into a
single node so the search can proceed, and is expanded afterward to recover the matching. For
maximum-weight matching in bipartite graphs, use Hungarian or min-cost max-flow; for maximum-weight
matching in general graphs, use the weighted blossom algorithm in the next section.
The graph must be simple: self-loops and parallel edges are not supported.
- `max_matching(adj)` returns a matching for a bidirectionally pre-populated adjacency list `adj`,
whose indices represent the nodes. The returned vector `match` has `match[u] == v` and
`match[v] == u` when `u` and `v` are matched, or `match[u]` is $-1$ when `u` is unmatched.
Time Complexity:
- O(n^3) per call, where $n$ is the number of nodes.
Space Complexity:
- O(max(n, m)) for storage of the graph, where $n$ is the number of nodes and $m$ is the number of
edges.
- O(n) auxiliary.
*/
#include <algorithm>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>
std::vector<int> max_matching(const std::vector<std::vector<int>> &adj) {
int n = static_cast<int>(adj.size());
std::vector<int> match(n, -1), label(n), parent(n), base(n), aux(n, -1);
std::queue<int> q;
int aux_time = -1;
auto lca = [&](int u, int v) {
aux_time++;
while (true) {
if (u != -1) {
u = base[u];
if (aux[u] == aux_time) {
return u;
}
aux[u] = aux_time;
u = (match[u] == -1) ? -1 : parent[match[u]];
}
std::swap(u, v);
}
};
auto mark_blossom = [&](int u, int v, int b) {
while (base[u] != b) {
parent[u] = v;
v = match[u];
if (label[v] == 1) {
label[v] = 0;
q.push(v);
}
base[u] = base[v] = b;
u = parent[v];
}
};
auto augment = [&](int u) {
while (u != -1) {
int p = parent[u];
int next = match[p];
match[u] = p;
match[p] = u;
u = next;
}
};
auto find_path = [&](int root) {
label.assign(n, -1);
parent.assign(n, -1);
std::iota(base.begin(), base.end(), 0);
while (!q.empty()) {
q.pop();
}
label[root] = 0;
q.push(root);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : adj[u]) {
if (base[u] == base[v] || match[u] == v) {
continue;
}
if (label[v] == -1) {
label[v] = 1;
parent[v] = u;
if (match[v] == -1) {
augment(v);
return true;
}
label[match[v]] = 0;
q.push(match[v]);
} else if (label[v] == 0) {
int b = lca(u, v);
mark_blossom(u, v, b);
mark_blossom(v, u, b);
}
}
}
return false;
};
for (int i = 0; i < n; i++) {
if (match[i] == -1) {
for (int v : adj[i]) {
if (i != v && match[v] == -1) {
match[i] = v;
match[v] = i;
break;
}
}
}
}
for (int i = 0; i < n; i++) {
if (match[i] == -1) {
find_path(i);
}
}
return match;
}
/*** Example Usage ***/
#include <cassert>
using namespace std;
vector<vector<int>> adj;
void add_edge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
int matching_size(const vector<int> &match) {
return count_if(match.begin(), match.end(), [](int v) { return v != -1; }) / 2;
}
int main() {
{
// 0---1
// | |
// 3---2
int nodes = 4;
adj.assign(nodes, {});
add_edge(0, 1);
add_edge(1, 2);
add_edge(2, 3);
add_edge(3, 0);
vector<int> match = max_matching(adj);
assert(matching_size(match) == 2);
assert((match == vector<int>{1, 0, 3, 2}));
}
{
// 3---0---1---4
// \ /
// 2
int nodes = 5;
adj.assign(nodes, {});
add_edge(0, 1);
add_edge(1, 2);
add_edge(2, 0);
add_edge(0, 3);
add_edge(1, 4);
assert(matching_size(max_matching(adj)) == 2);
}
return 0;
}