Matching basics
A matching pairs up vertices so that no vertex is used twice. Alternating and augmenting paths are the tools that improve a matching, and the difference between a maximal and a maximum matching explains why greedy pairing is not enough.
Learning goals
- Define a matching and tell matched from free vertices
- Recognise alternating and augmenting paths
- Flip an augmenting path and see why the matching grows by one
- Explain why a maximal matching need not be maximum
Prerequisites
Whenever things must be paired off, such as students with projects, jobs with workers or tiles with squares, each thing may be used once. That is a matching. The interesting question is how to pair as many as possible, and the answer is a surprisingly simple local move: find a path that lets you re-pair a few vertices and gain one more pair.
Matched/unmatched vertices
A **matching** in an undirected graph is a set of edges that share no vertex. Every vertex is touched by at most one chosen edge. A vertex that is an end of a chosen edge is **matched** (its other end is its **mate**); every other vertex is **free**, also called unmatched or exposed.
The **size** of a matching is its number of edges, not its number of matched vertices. A matching of size k matches exactly 2k vertices, so a graph with n vertices has a matching of size at most n / 2. A **perfect** matching leaves no vertex free (only possible when n is even); a **near-perfect** matching leaves exactly one.
In this chapter a matching is stored in one of two equivalent ways: as a list of edges {a, b}, or as an array mate[v] holding the partner of v or -1 for a free vertex. The array form makes the questions the algorithms ask, such as “is v free?” and “who is v paired with?”, constant-time.
| Term | Meaning | Example in a graph with edges 0-1, 1-2, 2-3 |
|---|---|---|
| Matching | edges that share no vertex | {0-1, 2-3} |
| Matched vertex | touched by a chosen edge | 0, 1, 2, 3 |
| Free vertex | touched by no chosen edge | with {1-2}: 0 and 3 |
| Size | number of chosen edges | 2 for {0-1, 2-3} |
| Perfect matching | no free vertex | {0-1, 2-3} |
1// a matching is a list of edges {a, b}; it is valid when no vertex appears twice2static boolean isMatching(int n, int[][] matching) {3boolean[] used = new boolean[n];4for (int[] e : matching) {5if (e[0] == e[1] || used[e[0]] || used[e[1]]) return false;6used[e[0]] = true;7used[e[1]] = true;8}9return true;10}11static List<Integer> freeVertices(int n, int[][] matching) { // the unmatched vertices12boolean[] used = new boolean[n];13for (int[] e : matching) { used[e[0]] = true; used[e[1]] = true; }14List<Integer> free = new ArrayList<>();15for (int v = 0; v < n; v++) if (!used[v]) free.add(v);16return free;17}
Alternating path
An **alternating path** is a path whose edges alternate between edges that are **not** in the matching and edges that **are**. Starting from a free vertex, the first edge is a non-matching edge, the second is a matching edge, the third non-matching, and so on.
Think of it as a re-pairing proposal. Moving along a non-matching edge says “let these two be partners”; the next matching edge says “then this vertex loses its old partner”, and that partner needs a new one, which the following non-matching edge supplies. An alternating path is exactly a chain of such swaps.
In a bipartite graph an alternating search from a free left vertex has a rigid shape: it leaves a left vertex along any non-matching edge, arrives at a right vertex, and must leave along that vertex's unique matching edge back to the left side. This is the search the next lessons use.
- Edge number 1, 3, 5, … (counting from 1) are non-matching edges; edge number 2, 4, 6, … are matching edges.
- An alternating path may start at a matched vertex too; it is **augmenting** only when both ends are free.
- An alternating **cycle** returns to its start; flipping it keeps the size but changes which edges are used.
1// mate[v] = partner of v, or -1 when v is free; the path is a vertex sequence2static boolean isAlternating(int[] path, int[] mate) { // free, matched, free, matched, ...3for (int i = 0; i + 1 < path.length; i++) {4boolean matched = mate[path[i]] == path[i + 1];5if (matched != (i % 2 == 1)) return false; // odd-numbered edges must be matching edges6}7return true;8}
Augmenting path
An **augmenting path** is an alternating path that starts at a free vertex and ends at a different free vertex. It therefore has an odd number of edges: k matching edges and k + 1 non-matching edges for some k ≥ 0.
**Flipping** the path swaps the roles of its edges: the k + 1 non-matching edges become matching edges and the k matching edges stop being matching edges. Interior vertices stay matched (they just get a new mate) and the two free end vertices become matched. The matching grows by exactly one edge.
**Berge's theorem** states the whole idea in one line: a matching is maximum if and only if the graph has no augmenting path for it. Every matching algorithm in this chapter is therefore a way of searching for augmenting paths until none is left. Open the animation and load “Greedy gets stuck; a path of length 5 fixes it”: after the greedy step the matching has 2 edges, a single augmenting path of 5 edges is found from vertex 0, and flipping it gives 3.
1static boolean isAugmenting(int[] path, int[] mate) { // mate[v] = partner or -12if (path.length < 2 || path.length % 2 == 1) return false; // an augmenting path has an odd number of edges3if (mate[path[0]] != -1 || mate[path[path.length - 1]] != -1) return false; // both ends are free4for (int i = 0; i + 1 < path.length; i++)5if ((mate[path[i]] == path[i + 1]) != (i % 2 == 1)) return false; // edges alternate: free, matched, free, ...6return true;7}8static void augment(int[] path, int[] mate) { // flip the path: one more matched edge9for (int i = 0; i + 1 < path.length; i += 2) {10mate[path[i]] = path[i + 1];11mate[path[i + 1]] = path[i];12}13}
Matchings: greedy maximal matching, then augmenting paths
Interactive · edit the inputleft vertices 0, 1, … and right vertices 0′, 1′, …; green edges are matched · 6 vertices · 5 edges
free
free
free
free
free
free
mate of each left vertex
mate of each right vertex
A matching is a set of edges that share no vertex. Step 1 scans the edges in the order written and takes every edge whose two ends are both free; step 2 looks for augmenting paths.
1@SuppressWarnings("unchecked")2static int maxMatching(int left, int right, int[][] edges) { // edges {leftVertex, rightVertex}3List<Integer>[] adj = new List[left];4for (int u = 0; u < left; u++) adj[u] = new ArrayList<>();5for (int[] e : edges) adj[e[0]].add(e[1]);6int[] matchL = new int[left], matchR = new int[right];7Arrays.fill(matchL, -1);8Arrays.fill(matchR, -1);9int size = 0;10for (int[] e : edges) { // 1. greedy: a maximal matching, edges in input order11if (matchL[e[0]] == -1 && matchR[e[1]] == -1) { matchL[e[0]] = e[1]; matchR[e[1]] = e[0]; size++; }12}13for (int root = 0; root < left; root++) { // 2. try to augment from every free left vertex14if (matchL[root] != -1) continue;15int[] parent = new int[right]; // parent[v]: left vertex from which v was reached16Arrays.fill(parent, -1);17Deque<Integer> queue = new ArrayDeque<>(List.of(root));18int end = -1;19while (!queue.isEmpty() && end == -1) { // alternating BFS20int u = queue.poll();21for (int v : adj[u]) {22if (parent[v] != -1) continue;23parent[v] = u; // arrive at v by a non-matching edge24if (matchR[v] == -1) { end = v; break; } // free right vertex: augmenting path found25queue.add(matchR[v]); // leave v by its matching edge26}27}28if (end == -1) continue; // no augmenting path from root, ever29for (int v = end; v != -1; ) { // flip the path30int u = parent[v], previous = matchL[u];31matchL[u] = v;32matchR[v] = u;33v = previous;34}35size++;36}37return size;38}
Live state
left
3
right
3
edges
5
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | left=3 right=3 edges=5 | A matching is a set of edges that share no vertex. Step 1 scans the edges in the order written and takes every edge whose two ends are both free; step 2 looks for augmenting paths. |
| 2 | L11 | edge=1-0′ taken=yes size=1 | Edge 1-0′: both ends are free, so it joins the matching (size 1). |
| 3 | L11 | edge=2-1′ taken=yes size=2 | Edge 2-1′: both ends are free, so it joins the matching (size 2). |
| 4 | L11 | edge=0-0′ taken=no size=2 | Edge 0-0′: right 0′ is already matched, so the edge is skipped. |
| 5 | L11 | edge=1-1′ taken=no size=2 | Edge 1-1′: left 1 is already matched, so the edge is skipped. |
| 6 | L11 | edge=2-2′ taken=no size=2 | Edge 2-2′: left 2 is already matched, so the edge is skipped. |
| 7 | L13 | greedy=2 free_left=1 | Greedy stops with 2 edges. The matching is maximal: no remaining edge has two free ends. That does not make it maximum, so augmenting paths are searched from each free left vertex. |
| 8 | L15 | root=0 | Alternating search from the free left vertex 0: leave a left vertex by any non-matching edge, leave a right vertex by its matching edge. |
| 9 | L25 | u=0 v=0′ next=1 | 0′ is matched to 1, so the search continues from 1. |
| 10 | L25 | u=1 v=1′ next=2 | 1′ is matched to 2, so the search continues from 2. |
| 11 | L24 | u=2 v=2′ end=2′ | Right vertex 2′ is free: an augmenting path from 0 to 2′ exists (it alternates free, matched, free edges). |
| 12 | L35 | size=3 augmentations=1 | Flipping the path adds one edge: the matching now has 3. Every vertex that was matched stays matched. |
| 13 | L37 | return=3 greedy=2 augmentations=1 | Maximum matching size 3. Greedy had 2; 1 augmentation raised it to 3. No augmenting path remains, which is exactly the condition for a maximum matching. |
Maximum vs maximal matching
A matching is **maximal** when no further edge can be added without breaking the matching: every edge has at least one matched end. A matching is **maximum** when no other matching is larger. Maximum implies maximal, but not the other way round.
The greedy rule “scan the edges and take every edge whose ends are both free” always produces a maximal matching, and it is fast, but it can be unlucky. With edges 0-0′, 0-1′, 1-0′ (left vertices 0, 1, right vertices 0′, 1′) greedy takes 0-0′ first and is then stuck at size 1, although 0-1′ and 1-0′ form a matching of size 2. Run the animation with “Greedy is stuck at 1; one swap reaches 2” to see the augmenting path 1 → 0′ → 0 → 1′ repair it.
The damage greedy can do is bounded: every maximal matching has at least **half** the size of a maximum matching, because each edge of a maximum matching shares a vertex with some edge of a maximal one, and each edge of the maximal matching can block at most two of them. So greedy is a 2-approximation, which is fine for rough answers but not for exact ones.
| Kind | Condition | Found by |
|---|---|---|
| Maximal | no edge can be added | one greedy scan, O(E) |
| Maximum | no larger matching exists | augmenting paths until none remain |
| Perfect | every vertex matched | a maximum matching that happens to use all vertices |
1static int[][] greedyMaximal(int n, int[][] edges) { // takes edges in the given order2boolean[] used = new boolean[n];3List<int[]> matching = new ArrayList<>();4for (int[] e : edges) {5if (e[0] != e[1] && !used[e[0]] && !used[e[1]]) {6used[e[0]] = true;7used[e[1]] = true;8matching.add(e);9}10}11return matching.toArray(new int[0][]);12}13static boolean isMaximal(int n, int[][] edges, int[][] matching) { // no edge could still be added14boolean[] used = new boolean[n];15for (int[] e : matching) { used[e[0]] = true; used[e[1]] = true; }16for (int[] e : edges) if (e[0] != e[1] && !used[e[0]] && !used[e[1]]) return false;17return true;18}
Worst
greedy O(V + E); a maximum matching needs the algorithms of the next lessons
Space
O(V)
Practice ladder
Now try it yourself
Easy
- Assign Cookies
Medium
- Maximum Number of Accepted Invitations
Advanced
- Maximum Students Taking Exam
Key takeaways
- A matching uses each vertex at most once; its size counts edges.
- An augmenting path alternates free/matched edges between two free vertices, and flipping it adds one edge.
- A matching is maximum exactly when no augmenting path exists (Berge); a maximal matching is only a lower-bound guess.