Hopcroft–Karp algorithm
Maximum bipartite matching in phases: each phase layers the graph with BFS, then finds several short, separate augmenting paths with DFS.
The simple matching method (one augmenting path per node) costs O(V · E) in the worst case. Hopcroft and Karp
(1973) find several augmenting paths together in each phase:
- Time:
O(E √V) - Space:
O(V) - CLRS, section 25.1
The same workers and jobs as bipartite matching; phase 1 matches several at once and phase 2 finds the rest with longer paths.
from collections import dequeINF = float("inf")def hopcroft_karp(graph, src):# Side 0 is found by two-colouring each component (the input is bipartite).side = {}for s in graph:if s in side:continueside[s] = 0stack = [s]while stack:u = stack.pop()for v, _ in graph[u]:if v not in side:side[v] = 1 - side[u]stack.append(v)left = [v for v in graph if side[v] == 0]match = {v: None for v in graph}size = 0phase = 0while True:phase = phase + 1dist, found = layers(graph, left, match)if not found:breakfor u in left:if match[u] is None:if augment(graph, u, match, dist):size = size + 1return size, matchdef layers(graph, left, match):dist = {}queue = deque()for u in left:if match[u] is None:dist[u] = 0queue.append(u)found = Falsewhile queue:u = queue.popleft()for v, _ in graph[u]:w = match[v]if w is None:found = Trueelif w not in dist:dist[w] = dist[u] + 1queue.append(w)return dist, founddef augment(graph, u, match, dist):for v, _ in graph[u]:w = match[v]if w is None:match[u] = vmatch[v] = ureturn Trueif dist.get(w) == dist[u] + 1:if augment(graph, w, match, dist):match[u] = vmatch[v] = ureturn Truedist[u] = INFreturn False
Start
- Current node
(u) - Matched edges
- Edge being checked
Recursion tree
Filled orange = running call · Orange outline = still on the stack · Grey = finished
Local variables
Orange = changed in this step
graph dict
- 'A': [('W', 1), ('X', 1)]
- 'B': [('W', 1)]
- 'C': [('X', 1), ('W', 1), ('Y', 1)]
- 'D': [('Y', 1), ('Z', 1)]
- 'W': [('A', 1), ('B', 1), ('C', 1)]
- 'X': [('A', 1), ('C', 1)]
- 'Y': [('C', 1), ('D', 1)]
- 'Z': [('D', 1)]
Call stack
- hopcroft_karp()line 6
How the work grows with input size
Average of 5 random graphs per n (fixed seeds). Hollow marker = the input on screen.
- phase
- layer
- current input (n=8)
Show as a table
| n | phase | layer |
|---|---|---|
| 4 | 2 | 0 |
| 8 | 2.8 | 2.4 |
| 12 | 2.4 | 2 |
| 16 | 3.2 | 8.2 |
| 20 | 3 | 8 |
Explanation
The idea
The simple matching method (one augmenting path per node) costs O(V · E) in the worst case. Hopcroft and Karp
(1973) find several augmenting paths together in each phase:
- A BFS from every free left node layers the graph until it reaches a free right node. A node's layer is the length of the shortest augmenting path to it.
- A DFS from each free left node only goes one layer deeper at a time; so only shortest paths are found. A node that hits a dead end is set aside for the rest of the phase.
Why it is fast
After each phase the shortest augmenting path gets at least one step longer, and one can show only O(√V) phases
are needed. Each phase costs O(E): O(E √V) in total.
What to watch in the trace
- The number by each left node is its BFS layer; the coloured edges are the current matching.
- Use the "every loop round" detail level to go phase by phase.