AlgoVerse

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:

Read the full explanation ↓
Input:
V=8 · E=8 · src=APDF export

The same workers and jobs as bipartite matching; phase 1 matches several at once and phase 2 finds the rest with longer paths.

  1. from collections import deque
  2. INF = float("inf")
  3. def hopcroft_karp(graph, src):
  4. # Side 0 is found by two-colouring each component (the input is bipartite).
  5. side = {}
  6. for s in graph:
  7. if s in side:
  8. continue
  9. side[s] = 0
  10. stack = [s]
  11. while stack:
  12. u = stack.pop()
  13. for v, _ in graph[u]:
  14. if v not in side:
  15. side[v] = 1 - side[u]
  16. stack.append(v)
  17. left = [v for v in graph if side[v] == 0]
  18. match = {v: None for v in graph}
  19. size = 0
  20. phase = 0
  21. while True:
  22. phase = phase + 1
  23. dist, found = layers(graph, left, match)
  24. if not found:
  25. break
  26. for u in left:
  27. if match[u] is None:
  28. if augment(graph, u, match, dist):
  29. size = size + 1
  30. return size, match
  31. def layers(graph, left, match):
  32. dist = {}
  33. queue = deque()
  34. for u in left:
  35. if match[u] is None:
  36. dist[u] = 0
  37. queue.append(u)
  38. found = False
  39. while queue:
  40. u = queue.popleft()
  41. for v, _ in graph[u]:
  42. w = match[v]
  43. if w is None:
  44. found = True
  45. elif w not in dist:
  46. dist[w] = dist[u] + 1
  47. queue.append(w)
  48. return dist, found
  49. def augment(graph, u, match, dist):
  50. for v, _ in graph[u]:
  51. w = match[v]
  52. if w is None:
  53. match[u] = v
  54. match[v] = u
  55. return True
  56. if dist.get(w) == dist[u] + 1:
  57. if augment(graph, w, match, dist):
  58. match[u] = v
  59. match[v] = u
  60. return True
  61. dist[u] = INF
  62. return False

Invariant: In each phase, all augmenting paths found have the shortest possible length and share no node.

Start

ABCDWXYZ
  • Current node (u)
  • Matched edges
  • Edge being checked

Recursion tree

Filled orange = running call · Orange outline = still on the stack · Grey = finished

Local variables

src'A'

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

  1. hopcroft_karp()line 6
Step 1 of 372
Black = phase · Orange = key stepfree: 0/5 · reach_free: 0/9 · phase: 0/3 · take: 0/4 · grown: 0/4 · dead_end: 0/1 · layer: 0/3 · reroute: 0/3 · no_path: 0/1

    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)
    051048121620n = number of nodeslayerphase
    Show as a table
    nphaselayer
    420
    82.82.4
    122.42
    163.28.2
    2038
    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:

    1. 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.
    2. 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.