General matching
In non-bipartite graphs an alternating search can meet itself at an odd cycle. Edmonds' blossom algorithm contracts such cycles and keeps searching, and Tutte's theorem describes exactly when a perfect matching exists.
Learning goals
- Explain why the bipartite search fails on odd cycles
- Describe blossom contraction and expansion
- Run the blossom algorithm on small graphs
- State Tutte's theorem and the Tutte-Berge formula
Prerequisites
Roommate pairing, tournament scheduling and many chemistry problems pair elements of a single set, so the graph is not bipartite. Berge's theorem still holds, but the search for an augmenting path breaks on odd cycles. The fix, found by Edmonds in 1965, is one of the landmark algorithms of combinatorial optimisation.
Blossom algorithm overview
The **blossom algorithm** finds a maximum matching in any undirected graph. For every free vertex root it grows an alternating tree: vertices at even depth (the root and the vertices reached through a matching edge) are scanned, and vertices at odd depth are labelled with a parent. If the scan reaches a free vertex, an augmenting path has been found and is flipped. If it meets an edge that joins two even vertices, an odd cycle has been found; the algorithm **contracts** it (next section) and continues.
The Java class below follows the well-known O(V^3) implementation. parent[] stores tree edges, base[v] is the representative of the blossom containing v, and findPath returns the free vertex that ends an augmenting path, or -1. When the path is flipped, the walk goes through parent[] and match[] alternately; blossoms need no special expansion code because parent was rewired when each blossom was contracted.
Run the animation on the default graph 0-1 1-2 2-0 2-3 3-4 4-5. The first search matches 0 with 1. The search from vertex 2 sees the triangle {0, 1, 2} (with 0-1 matched), contracts it into one vertex with base 2, and then finds the free vertex 3. Later 4 is matched with 5, for a maximum matching of size 3. Other presets: “Triangle: one vertex must stay free” shows a blossom that never leads to a path (size 1), and “Bipartite graph” shows that no contraction is needed when there are no odd cycles.
1static class Blossom {2final int n;3final List<Integer>[] g;4final int[] match, parent, base; // base[v]: representative of v's (contracted) blossom5final boolean[] inQueue, inBlossom;6@SuppressWarnings("unchecked")7Blossom(int n, int[][] edges) {8this.n = n;9g = new List[n];10for (int i = 0; i < n; i++) g[i] = new ArrayList<>();11for (int[] e : edges) { g[e[0]].add(e[1]); g[e[1]].add(e[0]); }12match = new int[n]; parent = new int[n]; base = new int[n];13inQueue = new boolean[n]; inBlossom = new boolean[n];14Arrays.fill(match, -1);15}16int lca(int a, int b) { // lowest common ancestor of a and b in the alternating tree17boolean[] seen = new boolean[n];18while (true) { a = base[a]; seen[a] = true; if (match[a] == -1) break; a = parent[match[a]]; }19while (true) { b = base[b]; if (seen[b]) return b; b = parent[match[b]]; }20}21void markPath(int v, int b, int child) { // walk from v up to the blossom base b22while (base[v] != b) {23inBlossom[base[v]] = inBlossom[base[match[v]]] = true;24parent[v] = child;25child = match[v];26v = parent[match[v]];27}28}29int findPath(int root) { // end of an augmenting path from root, or -130Arrays.fill(inQueue, false);31Arrays.fill(parent, -1);32for (int i = 0; i < n; i++) base[i] = i;33inQueue[root] = true;34Deque<Integer> queue = new ArrayDeque<>(List.of(root));35while (!queue.isEmpty()) {36int v = queue.poll();37for (int to : g[v]) {38if (base[v] == base[to] || match[v] == to) continue;39if (to == root || (match[to] != -1 && parent[match[to]] != -1)) { // an odd cycle: contract the blossom40int cur = lca(v, to);41Arrays.fill(inBlossom, false);42markPath(v, cur, to);43markPath(to, cur, v);44for (int i = 0; i < n; i++) if (inBlossom[base[i]]) {45base[i] = cur;46if (!inQueue[i]) { inQueue[i] = true; queue.add(i); }47}48} else if (parent[to] == -1) {49parent[to] = v;50if (match[to] == -1) return to; // a free vertex: augmenting path found51inQueue[match[to]] = true;52queue.add(match[to]);53}54}55}56return -1;57}58int maxMatching() {59int size = 0;60for (int root = 0; root < n; root++) {61if (match[root] != -1) continue;62int end = findPath(root);63if (end == -1) continue;64size++;65while (end != -1) { // flip the path, expanding blossoms through parent[]66int pv = parent[end], next = match[pv];67match[end] = pv;68match[pv] = end;69end = next;70}71}72return size;73}74}
Worst
O(V³) for this implementation (O(E · √V) with Micali-Vazirani)
Space
O(V + E)
Edmonds' blossom algorithm: maximum matching in a general graph
Interactive · edit the inputundirected graph · 6 vertices · 6 edges
free
free
free
free
free
free
mate of each vertex
base of each vertex (blossom representative)
In a general graph an alternating search can meet itself at an odd cycle. The blossom algorithm contracts such a cycle into one vertex and keeps searching; this is what makes augmenting-path search correct beyond bipartite graphs.
1static class Blossom {2final int n;3final List<Integer>[] g;4final int[] match, parent, base; // base[v]: representative of v's (contracted) blossom5final boolean[] inQueue, inBlossom;6@SuppressWarnings("unchecked")7Blossom(int n, int[][] edges) {8this.n = n;9g = new List[n];10for (int i = 0; i < n; i++) g[i] = new ArrayList<>();11for (int[] e : edges) { g[e[0]].add(e[1]); g[e[1]].add(e[0]); }12match = new int[n]; parent = new int[n]; base = new int[n];13inQueue = new boolean[n]; inBlossom = new boolean[n];14Arrays.fill(match, -1);15}16int lca(int a, int b) { // lowest common ancestor of a and b in the alternating tree17boolean[] seen = new boolean[n];18while (true) { a = base[a]; seen[a] = true; if (match[a] == -1) break; a = parent[match[a]]; }19while (true) { b = base[b]; if (seen[b]) return b; b = parent[match[b]]; }20}21void markPath(int v, int b, int child) { // walk from v up to the blossom base b22while (base[v] != b) {23inBlossom[base[v]] = inBlossom[base[match[v]]] = true;24parent[v] = child;25child = match[v];26v = parent[match[v]];27}28}29int findPath(int root) { // end of an augmenting path from root, or -130Arrays.fill(inQueue, false);31Arrays.fill(parent, -1);32for (int i = 0; i < n; i++) base[i] = i;33inQueue[root] = true;34Deque<Integer> queue = new ArrayDeque<>(List.of(root));35while (!queue.isEmpty()) {36int v = queue.poll();37for (int to : g[v]) {38if (base[v] == base[to] || match[v] == to) continue;39if (to == root || (match[to] != -1 && parent[match[to]] != -1)) { // an odd cycle: contract the blossom40int cur = lca(v, to);41Arrays.fill(inBlossom, false);42markPath(v, cur, to);43markPath(to, cur, v);44for (int i = 0; i < n; i++) if (inBlossom[base[i]]) {45base[i] = cur;46if (!inQueue[i]) { inQueue[i] = true; queue.add(i); }47}48} else if (parent[to] == -1) {49parent[to] = v;50if (match[to] == -1) return to; // a free vertex: augmenting path found51inQueue[match[to]] = true;52queue.add(match[to]);53}54}55}56return -1;57}58int maxMatching() {59int size = 0;60for (int root = 0; root < n; root++) {61if (match[root] != -1) continue;62int end = findPath(root);63if (end == -1) continue;64size++;65while (end != -1) { // flip the path, expanding blossoms through parent[]66int pv = parent[end], next = match[pv];67match[end] = pv;68match[pv] = end;69end = next;70}71}72return size;73}74}
Live state
n
6
edges
6
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=6 edges=6 | In a general graph an alternating search can meet itself at an odd cycle. The blossom algorithm contracts such a cycle into one vertex and keeps searching; this is what makes augmenting-path search correct beyond bipartite graphs. |
| 2 | L33 | root=0 | Grow an alternating tree from the free vertex 0. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 3 | L50 | v=0 to=1 | 1 is free: the alternating path from 0 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 4 | L64 | size=1 | The path is flipped: the matching grows to 1. |
| 5 | L33 | root=2 | Grow an alternating tree from the free vertex 2. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 6 | L49 | v=2 to=1 mate=0 | Label 1 odd (parent 2); its mate 0 becomes an even vertex and is queued. |
| 7 | L40 | v=2 to=0 base=2 blossom=0,1 | Edge 2-0 joins two even vertices of the tree, closing an odd cycle. Contract it: vertices {0, 1} now share the base 2 and every odd vertex in it becomes even (it joins the queue). |
| 8 | L50 | v=2 to=3 | 3 is free: the alternating path from 2 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 9 | L64 | size=2 | The path is flipped: the matching grows to 2. |
| 10 | L33 | root=4 | Grow an alternating tree from the free vertex 4. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 11 | L49 | v=4 to=3 mate=2 | Label 3 odd (parent 4); its mate 2 becomes an even vertex and is queued. |
| 12 | L50 | v=4 to=5 | 5 is free: the alternating path from 4 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 13 | L64 | size=3 | The path is flipped: the matching grows to 3. |
| 14 | L72 | return=3 blossoms=1 augmentations=3 | Maximum matching size 3 (0 vertices stay free), with 1 blossom contracted along the way. |
Odd-cycle contraction
A **blossom** is an odd cycle of length 2k + 1 containing exactly k matching edges, reached from the tree through its **base**, the one cycle vertex not matched inside the cycle. Every vertex of a blossom can be reached from the base by an alternating path of even length in two ways, one around each side of the cycle. That is why all its vertices behave like even vertices once the blossom is found.
**Contracting** a blossom replaces its vertices by one super-vertex. The key lemma is that the original graph has an augmenting path (relative to the matching) exactly when the contracted graph has one relative to the contracted matching. So the search can safely continue in the smaller graph, and when a path is found it is expanded back into the original cycle, choosing the way around the cycle that makes the alternation work.
In the code the contraction is done lazily: lca finds the base, markPath marks the vertices on both sides and rewires parent so that either way round is alternating, and base[i] = cur merges them. Odd vertices of the blossom become even and join the queue. Bipartite graphs have no odd cycles at all, which is why the plain alternating BFS of the previous lessons is enough for them. The check below tests that condition with a 2-colouring: false means the graph contains an odd cycle, so a blossom is possible.
| Vertex label in the tree | How it is reached | What the search does with it |
|---|---|---|
| Even (root or mate of an odd vertex) | root, or the matching edge from an odd vertex | scan its edges |
| Odd | non-matching edge from an even vertex | label with parent, queue its mate |
| Inside a blossom | merged by contraction | treated as even; shares one base |
| Free and reached as odd | non-matching edge | end of an augmenting path |
1static boolean isBipartite(int n, List<Integer>[] adj) { // false means an odd cycle exists2int[] colour = new int[n]; // 0 = unseen; 1 and 2 are the two sides3for (int s = 0; s < n; s++) {4if (colour[s] != 0) continue;5colour[s] = 1;6Deque<Integer> queue = new ArrayDeque<>(List.of(s));7while (!queue.isEmpty()) {8int u = queue.poll();9for (int v : adj[u]) {10if (colour[v] == colour[u]) return false; // an edge inside one side closes an odd cycle11if (colour[v] == 0) { colour[v] = 3 - colour[u]; queue.add(v); }12}13}14}15return true;16}
Edmonds' blossom algorithm: maximum matching in a general graph
Interactive · edit the inputundirected graph · 6 vertices · 6 edges
free
free
free
free
free
free
mate of each vertex
base of each vertex (blossom representative)
In a general graph an alternating search can meet itself at an odd cycle. The blossom algorithm contracts such a cycle into one vertex and keeps searching; this is what makes augmenting-path search correct beyond bipartite graphs.
1static class Blossom {2final int n;3final List<Integer>[] g;4final int[] match, parent, base; // base[v]: representative of v's (contracted) blossom5final boolean[] inQueue, inBlossom;6@SuppressWarnings("unchecked")7Blossom(int n, int[][] edges) {8this.n = n;9g = new List[n];10for (int i = 0; i < n; i++) g[i] = new ArrayList<>();11for (int[] e : edges) { g[e[0]].add(e[1]); g[e[1]].add(e[0]); }12match = new int[n]; parent = new int[n]; base = new int[n];13inQueue = new boolean[n]; inBlossom = new boolean[n];14Arrays.fill(match, -1);15}16int lca(int a, int b) { // lowest common ancestor of a and b in the alternating tree17boolean[] seen = new boolean[n];18while (true) { a = base[a]; seen[a] = true; if (match[a] == -1) break; a = parent[match[a]]; }19while (true) { b = base[b]; if (seen[b]) return b; b = parent[match[b]]; }20}21void markPath(int v, int b, int child) { // walk from v up to the blossom base b22while (base[v] != b) {23inBlossom[base[v]] = inBlossom[base[match[v]]] = true;24parent[v] = child;25child = match[v];26v = parent[match[v]];27}28}29int findPath(int root) { // end of an augmenting path from root, or -130Arrays.fill(inQueue, false);31Arrays.fill(parent, -1);32for (int i = 0; i < n; i++) base[i] = i;33inQueue[root] = true;34Deque<Integer> queue = new ArrayDeque<>(List.of(root));35while (!queue.isEmpty()) {36int v = queue.poll();37for (int to : g[v]) {38if (base[v] == base[to] || match[v] == to) continue;39if (to == root || (match[to] != -1 && parent[match[to]] != -1)) { // an odd cycle: contract the blossom40int cur = lca(v, to);41Arrays.fill(inBlossom, false);42markPath(v, cur, to);43markPath(to, cur, v);44for (int i = 0; i < n; i++) if (inBlossom[base[i]]) {45base[i] = cur;46if (!inQueue[i]) { inQueue[i] = true; queue.add(i); }47}48} else if (parent[to] == -1) {49parent[to] = v;50if (match[to] == -1) return to; // a free vertex: augmenting path found51inQueue[match[to]] = true;52queue.add(match[to]);53}54}55}56return -1;57}58int maxMatching() {59int size = 0;60for (int root = 0; root < n; root++) {61if (match[root] != -1) continue;62int end = findPath(root);63if (end == -1) continue;64size++;65while (end != -1) { // flip the path, expanding blossoms through parent[]66int pv = parent[end], next = match[pv];67match[end] = pv;68match[pv] = end;69end = next;70}71}72return size;73}74}
Live state
n
6
edges
6
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=6 edges=6 | In a general graph an alternating search can meet itself at an odd cycle. The blossom algorithm contracts such a cycle into one vertex and keeps searching; this is what makes augmenting-path search correct beyond bipartite graphs. |
| 2 | L33 | root=0 | Grow an alternating tree from the free vertex 0. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 3 | L50 | v=0 to=1 | 1 is free: the alternating path from 0 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 4 | L64 | size=1 | The path is flipped: the matching grows to 1. |
| 5 | L33 | root=2 | Grow an alternating tree from the free vertex 2. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 6 | L49 | v=2 to=1 mate=0 | Label 1 odd (parent 2); its mate 0 becomes an even vertex and is queued. |
| 7 | L40 | v=2 to=0 base=2 blossom=0,1 | Edge 2-0 joins two even vertices of the tree, closing an odd cycle. Contract it: vertices {0, 1} now share the base 2 and every odd vertex in it becomes even (it joins the queue). |
| 8 | L50 | v=2 to=3 | 3 is free: the alternating path from 2 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 9 | L64 | size=2 | The path is flipped: the matching grows to 2. |
| 10 | L33 | root=4 | Grow an alternating tree from the free vertex 4. Even vertices (reached by a matching edge, or the root) are scanned; odd ones are only labelled. |
| 11 | L49 | v=4 to=3 mate=2 | Label 3 odd (parent 4); its mate 2 becomes an even vertex and is queued. |
| 12 | L50 | v=4 to=5 | 5 is free: the alternating path from 4 ends here (blossoms are expanded through parent[] when the path is flipped). |
| 13 | L64 | size=3 | The path is flipped: the matching grows to 3. |
| 14 | L72 | return=3 blossoms=1 augmentations=3 | Maximum matching size 3 (0 vertices stay free), with 1 blossom contracted along the way. |
Tutte theorem overview
**Tutte's theorem** characterises perfect matchings in any graph: G has a perfect matching if and only if for every subset U of vertices, the graph G − U has at most |U| connected components with an **odd** number of vertices. A set U that breaks the condition is a certificate that no perfect matching exists: each odd component needs one of its own vertices matched to something in U, and U is too small.
The **Tutte-Berge formula** gives the exact size of a maximum matching: ν(G) = (n − max over U of (odd(G − U) − |U|)) / 2. The quantity in the maximum is the number of vertices that must stay free (the deficiency). The code below evaluates the formula by brute force over all subsets, which is exponential and meant only for understanding and for testing the blossom algorithm on small graphs.
When the blossom algorithm stops, it essentially hands over such a certificate: the odd vertices of its final alternating forests form a set U whose removal leaves the odd components that explain the unmatched vertices. So Tutte's theorem plays for general graphs the role that Hall's theorem and König's theorem play for bipartite ones.
| Graph | Best U | odd(G − U) − |U| | Maximum matching |
|---|---|---|---|
| Triangle | ∅ | 1 − 0 = 1 | (3 − 1) / 2 = 1 |
| Path with 4 vertices | ∅ | 0 | 4 / 2 = 2 |
| Star with 3 leaves | centre | 3 − 1 = 2 | (4 − 2) / 2 = 1 |
| Even cycle | ∅ | 0 | n / 2 |
1// adj[v] = bit mask of the neighbours of v (n <= 16).2// Tutte-Berge: maximum matching = (n - max over U of (odd(G - U) - |U|)) / 2, where odd counts odd-sized components.3static int maxMatchingByTutteBerge(int n, int[] adj) {4int deficiency = 0;5for (int removed = 0; removed < (1 << n); removed++) {6int odd = 0, left = ((1 << n) - 1) & ~removed;7while (left != 0) { // peel off one component of G - removed at a time8int comp = left & -left, frontier = comp;9while (frontier != 0) {10int v = Integer.numberOfTrailingZeros(frontier);11frontier &= frontier - 1;12int fresh = adj[v] & left & ~comp;13comp |= fresh;14frontier |= fresh;15}16left &= ~comp;17if (Integer.bitCount(comp) % 2 == 1) odd++;18}19deficiency = Math.max(deficiency, odd - Integer.bitCount(removed));20}21return (n - deficiency) / 2;22}
Practice ladder
Now try it yourself
Easy
- Find Center of Star Graph
Medium
- Possible Bipartition
Advanced
- Maximum Students Taking Exam
Key takeaways
- An odd cycle (blossom) breaks the bipartite search; contract it and continue.
- A graph has an augmenting path iff its contraction has one, so contraction is safe.
- Tutte: no perfect matching iff some U leaves more than |U| odd components; Tutte-Berge gives the exact maximum.