A matching is a set of edges without common endpoints. Everything rests on Berge’s theorem: a matching is maximum iff it has no augmenting path (alternating path between two free vertices). In bipartite graphs augmenting paths are found by plain DFS/BFS, and the min-max theorems of König, Hall and Dilworth make matching a modelling tool for covers, independent sets and posets.
| Algorithm | Time | Notes |
|---|---|---|
| Kuhn (DFS augmenting paths) | , fast in practice | rounds with a shared visited array + greedy look-ahead |
| Hopcroft–Karp | phases of disjoint shortest augmenting paths | |
| Dinic on the unit network | reuse the flow template | |
| weighted | Hungarian / min-cost flow | Assignment Problem |
Kuhn
struct Kuhn {
int n, m, it = 0; vector<vector<int>> g; vector<int> ml, mr, vis;
Kuhn(int n, int m) : n(n), m(m), g(n), ml(n, -1), mr(m, -1), vis(n) {}
void addEdge(int u, int v) { g[u].push_back(v); }
bool dfs(int u) {
vis[u] = it;
for (int v : g[u]) if (mr[v] < 0) { ml[u] = v, mr[v] = u; return true; } // look-ahead: free neighbour
for (int v : g[u]) if (vis[mr[v]] != it && dfs(mr[v])) { ml[u] = v, mr[v] = u; return true; }
return false;
}
int run() {
int res = 0;
for (bool ch = true; ch;) { ch = false; it++; for (int u = 0; u < n; u++) if (ml[u] < 0 && dfs(u)) res++, ch = true; }
return res;
}
};The visited array is shared by a whole round; rounds repeat until one finds nothing. Shuffle adjacency lists against adversarial tests, and seed the matching with a cheap greedy pass first (match any left vertex to any free neighbour before the main loop) — this alone typically removes 70–90% of the augmenting searches.
Hopcroft–Karp
struct HopcroftKarp {
int n, m, lim; vector<vector<int>> g; vector<int> ml, mr, dist, it;
HopcroftKarp(int n, int m) : n(n), m(m), g(n), ml(n, -1), mr(m, -1), dist(n), it(n) {}
void addEdge(int u, int v) { g[u].push_back(v); }
bool bfs() {
queue<int> q; lim = INT_MAX;
for (int u = 0; u < n; u++) if (ml[u] < 0) dist[u] = 0, q.push(u); else dist[u] = INT_MAX;
while (sz(q)) {
int u = q.front(); q.pop();
if (dist[u] >= lim) continue;
for (int v : g[u]) { int w = mr[v]; if (w < 0) lim = min(lim, dist[u]); else if (dist[w] == INT_MAX) dist[w] = dist[u] + 1, q.push(w); }
}
return lim != INT_MAX;
}
bool dfs(int u) {
for (int& i = it[u]; i < sz(g[u]); i++) {
int v = g[u][i], w = mr[v];
if (w < 0 ? dist[u] == lim : dist[w] == dist[u] + 1 && dfs(w)) { ml[u] = v, mr[v] = u; return true; }
}
return false;
}
int run() {
int res = 0;
while (bfs()) { fill(all(it), 0); for (int u = 0; u < n; u++) if (ml[u] < 0 && dfs(u)) res++; }
return res;
}
};Proof: Berge's theorem and the algorithms
Berge. An augmenting path gives , larger by one. Conversely if is larger than , the components of have max degree 2 and alternate, so they are even cycles and paths; some path has more edges than edges, and such a path starts and ends with edges at vertices free in : an augmenting path for .
Kuhn. A DFS from a free left vertex explores exactly the alternating paths from (unmatched edge to the right, matched edge back). If it fails, no augmenting path starts at ; as long as the matching does not change, no vertex visited by a failed search can be on an augmenting path from a later start either (the search from it already failed), so sharing
viswithin a round is sound. A round with no success therefore certifies that no free left vertex starts an augmenting path; every augmenting path in a bipartite graph has an endpoint on the left, so by Berge the matching is maximum. Each round costs and the number of successful augmentations is at most .Hopcroft–Karp. Each phase BFS-layers the alternating graph from all free left vertices,
limis the length of shortest augmenting paths, and the DFS with current arcs finds a maximal set of vertex-disjoint shortest augmenting paths in . After the phase, the shortest augmenting path is strictly longer (the same argument as Dinic’s phases). After phases every augmenting path has length ; contains vertex-disjoint augmenting paths, each with vertices, so and at most more phases follow. Total .
Berge’s theorem holds verbatim for general (non-bipartite) graphs too — the matching theory above is specifically about finding the augmenting path by plain DFS/BFS, which relies on bipartiteness; on general graphs an odd cycle can hide the path and needs blossom contraction instead (General Matching). Online bipartite matching (right vertices, or edges, arrive over time and must be matched irrevocably) is a different problem entirely — greedy achieves only a ratio, and the optimal randomized ranking algorithm achieves .
König, Hall and the covering formulas
König: in a bipartite graph, max matching = min vertex cover. From a maximum matching, let be the vertices reachable from free left vertices by alternating paths; the cover is .
| Quantity | Value |
|---|---|
| minimum vertex cover | (max matching) |
| maximum independent set | (complement of a cover) |
| minimum edge cover (no isolated vertices) | (Gallai): matching + one edge per free vertex |
| maximum matching (König–Ore deficiency) | $ |
pair<vector<int>, vector<int>> konigCover(int n, int m, const vector<vector<int>>& g, const vector<int>& ml, const vector<int>& mr) {
vector<char> zl(n), zr(m); vector<int> st, L, R; // input: a maximum matching
for (int u = 0; u < n; u++) if (ml[u] < 0) zl[u] = 1, st.push_back(u);
while (sz(st)) {
int u = st.back(); st.pop_back();
for (int v : g[u]) if (!zr[v]) { zr[v] = 1; int w = mr[v]; if (w >= 0 && !zl[w]) zl[w] = 1, st.push_back(w); }
}
for (int u = 0; u < n; u++) if (!zl[u]) L.push_back(u);
for (int v = 0; v < m; v++) if (zr[v]) R.push_back(v);
return {L, R}; // |L| + |R| = matching size
}Hall: can be fully matched iff for all . Corollaries: every -regular bipartite graph () has a perfect matching, hence its edges split into perfect matchings; a “for every subset, enough neighbours” condition in a constructive problem means a matching exists.
Proof: König, Hall, Gallai, regular graphs
König. Every edge needs a cover vertex and matching edges are disjoint, so cover . For the construction: an edge with has (if unmatched, the alternating search crosses it; if matched, was reached through since free left vertices are unmatched), so every edge has or : it is a cover. Every is matched (else an augmenting path exists), and its partner is in ; every is matched (free ones are in ). No matching edge has both ends counted, since a matched puts its partner in . So the cover has exactly vertices.
Hall. Necessity is clear. If , take a König cover , , and . Edges from must be covered on the right, so and . The same computation gives the deficiency formula .
Gallai (edge cover). Given a maximum matching, adding one edge per free vertex gives edges. Conversely, a minimum edge cover is a forest of stars (removing an edge from a path of length 3 keeps it a cover); a forest of stars on vertices has edges and choosing one edge per star is a matching, so and the cover has edges.
-regular. For , the edges leaving all end in , which has only edge endpoints, so . Removing a perfect matching leaves a -regular bipartite graph; induct.
Which vertices and edges are forced
With a maximum matching , orient unmatched edges and matched edges . Let = vertices reachable from free left vertices, = vertices reachable from free right vertices in the reversed orientation.
- A left vertex is matched in every maximum matching iff it is not in (symmetrically on the right with ).
- An edge is in some maximum matching iff , or , or and lie in the same SCC of the oriented graph (an even alternating cycle). Edges of are trivially in some.
- An edge is in every maximum matching iff , , and are in different SCCs.
struct MatchingClassifier { // after a maximum matching (ml, mr)
vector<char> zl, zr; vector<int> comp; // comp over n + m vertices (right v -> n + v)
MatchingClassifier(int n, int m, const vector<vector<int>>& g, const vector<int>& ml, const vector<int>& mr) {
vector<vector<int>> gr(m); for (int u = 0; u < n; u++) for (int v : g[u]) gr[v].push_back(u);
zl.assign(n, 0); zr.assign(m, 0); vector<int> st;
for (int u = 0; u < n; u++) if (ml[u] < 0) zl[u] = 1, st.push_back(u);
while (sz(st)) { int u = st.back(); st.pop_back(); for (int v : g[u]) { int w = mr[v]; if (w >= 0 && !zl[w]) zl[w] = 1, st.push_back(w); } }
for (int v = 0; v < m; v++) if (mr[v] < 0) zr[v] = 1, st.push_back(v);
while (sz(st)) { int v = st.back(); st.pop_back(); for (int u : gr[v]) { int w = ml[u]; if (w >= 0 && !zr[w]) zr[w] = 1, st.push_back(w); } }
int N = n + m, timer = 0, nc = 0; comp.assign(N, -1); vector<int> num(N, -1), low(N), stk;
auto dfs = [&](auto& self, int x) -> void {
num[x] = low[x] = timer++; stk.push_back(x);
auto visit = [&](int y) { if (num[y] < 0) self(self, y), low[x] = min(low[x], low[y]); else if (comp[y] < 0) low[x] = min(low[x], num[y]); };
if (x < n) { for (int v : g[x]) if (ml[x] != v) visit(n + v); } // unmatched L -> R
else if (mr[x - n] >= 0) visit(mr[x - n]); // matched R -> L
if (low[x] == num[x]) { int y; do { y = stk.back(); stk.pop_back(); comp[y] = nc; } while (y != x); nc++; }
};
for (int x = 0; x < N; x++) if (num[x] < 0) dfs(dfs, x);
}
bool leftAvoidable(int u) const { return zl[u]; }
bool rightAvoidable(int v) const { return zr[v]; }
bool edgeInSome(int u, int v, const vector<int>& ml) const { return ml[u] == v || zl[u] || zr[v] || comp[u] == comp[sz(zl) + v]; }
bool edgeInEvery(int u, int v, const vector<int>& ml) const { return ml[u] == v && !zl[u] && !zr[v] && comp[u] != comp[sz(zl) + v]; }
};Proof: avoidable vertices and allowed edges
Vertices. If is reached from a free left by an even alternating path (ending with the matched edge into , or empty if ), then is a maximum matching leaving free. Conversely, let be maximum with free in but matched in . The component of at is a path starting at with an edge; it has even length (odd would augment one of them), so it ends at a left vertex that is covered by but free in . Read from , it is an alternating path using non- edges and edges , so .
Edges. If via from a free : if lies on , then from ‘s partner to plus closes an even alternating cycle; otherwise , and ‘s matched edge (v is matched, else augments) form an even alternating path from . Flipping either gives a maximum matching containing . The case is symmetric, and a same-SCC pair gives an alternating cycle through the edge (any cycle in this orientation alternates). Conversely, if a maximum contains , the component of through it is an even cycle (same SCC) or an even path; the path’s end that is free in is a left vertex (then reading the path from it reaches before , so ) or a right vertex (symmetrically ).
Forced edges. For : if (or ) some maximum matching leaves (or ) free, so it avoids the edge; if share an SCC, a cycle through must enter by its only incoming edge , and flipping that alternating cycle avoids the edge. Conversely, if a maximum avoids , the component of through it is an even cycle (same SCC) or an even path whose -free end is on the left (the path reaches then through the matched edge, so ) or on the right ().
Posets: Dilworth and Mirsky
- Dilworth: the minimum number of chains covering a poset equals the maximum antichain. Compute: transitive closure, split graph ( for ), chains ; the antichain is for a König cover .
- Mirsky (dual): the minimum number of antichains covering a poset equals the longest chain (group by the length of the longest chain ending at each element). For sequences this is “minimum number of non-increasing subsequences = LIS” (patience sorting).
- Disjoint paths vs chains: vertex-disjoint path cover of a DAG uses the split graph without closure (Flow Modeling).
vector<int> maxAntichain(int n, const vector<vector<char>>& less) { // less = transitive relation, less[x][y]: x < y
HopcroftKarp hk(n, n);
for (int x = 0; x < n; x++) for (int y = 0; y < n; y++) if (less[x][y]) hk.addEdge(x, y);
hk.run();
auto [cl, cr] = konigCover(n, n, hk.g, hk.ml, hk.mr);
vector<char> bad(n); for (int x : cl) bad[x] = 1; for (int y : cr) bad[y] = 1;
vector<int> res; for (int x = 0; x < n; x++) if (!bad[x]) res.push_back(x);
return res; // size = n - matching = min chain cover
}Proof: Dilworth and Mirsky
Dilworth. A chain cover with chains uses “successor” pairs forming a matching in the split graph (each element has at most one successor and predecessor in its chain), and conversely a matching gives chains because the relation is transitive (consecutive pairs compose). So min chains . An antichain meets each chain at most once, so antichain chains. For the construction, has vertices, so at most elements are excluded and the result has elements. It is an antichain: if were both kept, the edge would be uncovered. So it is a maximum antichain and both sides equal .
Mirsky. A chain meets each antichain at most once, so the number of antichains longest chain . Let be the number of elements of the longest chain ending at . If then , so each level is an antichain, and there are levels.
Greedy special cases
- Convex bipartite graphs (each left vertex is adjacent to an interval of ordered right vertices; e.g. jobs and time slots): sweep the right vertices in order and give each one to the available left vertex with the smallest (a heap). , optimal.
- Unit-interval / points on a line matchings are usually this greedy or a sorted DP.
int convexMatching(int m, vector<pair<int, int>> iv) { // left i adjacent to right [l, r] in 0..m-1
sort(all(iv)); priority_queue<int, vector<int>, greater<>> pq; int res = 0, k = 0;
for (int j = 0; j < m; j++) {
while (k < sz(iv) && iv[k].first == j) pq.push(iv[k++].second);
while (sz(pq) && pq.top() < j) pq.pop();
if (sz(pq)) pq.pop(), res++;
}
return res;
}Proof: earliest deadline first is optimal
Process right vertices in order. Suppose an optimal matching agrees with the greedy on all slots before , and the greedy gives slot to the available left vertex with the smallest . If the optimum leaves empty, re-assign to (freeing ‘s later slot, if any): same size. If the optimum gives to : if is unmatched in the optimum, swap out for ; otherwise is matched to a later slot , so can take (since ) and takes . If no left vertex is available, the slot is empty in every extension. Each step keeps an optimal matching agreeing with the greedy one slot further.
Modelling
- Rows and columns: placing non-attacking rooks on allowed cells = matching rows to columns; covering all marked cells with fewest lines = König cover.
- Grid graphs are bipartite (chessboard colouring): domino tilings, maximum independent set of cells, knights on a board.
- Perfect matching in general bipartite graphs with on one side: count with DP over subsets; the number of perfect matchings is a permanent (#P-hard in general).
- Bottleneck / threshold: binary search a parameter and test for a perfect matching.
- Vertex capacities (-matching): flow with capacities on source and sink edges.
- Preference-based pairing (“everyone ranks the other side”) is a different problem, not a maximum/min-cost matching — see Stable Matching (Gale–Shapley).