Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Graphs

Maximum bipartite matching

Lesson · 11 min read · Graphs · Bipartite matching

Pair applicants with jobs so that as many as possible are placed: a maximum flow with every capacity 1, or a short augmenting-path search.

The problem

Four applicants, four jobs, and a list of which applicant is qualified for which job. Each job takes one person and each person takes one job. How many applicants can be placed? A set of pairs with no applicant or job used twice is a matching; the largest is a maximum matching.

A1A2A3A4J1J2J3J4applicantsjobs
An edge means the applicant is qualified for the job.

Assigning greedily can strand people: give J1 to A1 first and A2 may have nothing left later. As with flow, the cure is to let later choices move earlier ones.

Matching as maximum flow

Add a source joined to every applicant and a sink joined from every job, and give every edge capacity 1. An integral flow of value k picks k applicant–job edges with no applicant or job used twice, and the reverse is true too, so the maximum flow is the size of the maximum matching.

sA1A2A3A4J1J2J3J4t
Every edge has capacity 1. The flow algorithm's reverse edges are exactly "move this applicant to another job".

Augmenting paths directly

Running a full flow algorithm is not necessary. The same idea, written for a bipartite graph, is short: for each applicant, look for a job that is free, or whose current holder can be moved to another job they qualify for, recursively. Each success is an augmenting path that alternates between unmatched and matched edges and adds one pair.

Augmenting-path matching (Kuhn)

tryAssign(a, seen)

  1. For each job j that applicant a qualifies for, not yet in seen: add j to seen.
  2. If j is free, or tryAssign(holder of j, seen) succeeds, give j to a and return true.
  3. Return false.

maxMatching

  1. For each applicant a: clear seen and call tryAssign(a, seen); count the successes.

Applicants are placed one at a time; when a job is taken, its holder is asked to move.

  1. Step 1 / 4
    A1A2A3A4J1J2J3J4
    matched1

    A1: augmenting path A1–J1. J1 was free. 1 matched.

  2. Step 2 / 4
    A1A2A3A4J1J2J3J4
    matched2

    A2: augmenting path A2–J1, A1–J2. To make room, A1 moves from J1 to J2. 2 matched.

  3. Step 3 / 4
    A1A2A3A4J1J2J3J4
    matched3

    A3: augmenting path A3–J2, A1–J1, A2–J3. To make room, A1 moves from J2 to J1; A2 moves from J1 to J3. 3 matched.

  4. Step 4 / 4
    A1A2A3A4J1J2J3J4
    matched4

    A4: augmenting path A4–J4. J4 was free. 4 matched. No more augmenting paths: the maximum matching has 4 pairs.

current matchingaugmenting path just usedcould not be placed

Implementation

The problem at the end of this module, with qualifications given as a 0/1 matrix:

import sys

def max_matching(matrix):
    sys.setrecursionlimit(10000)
    n, m = len(matrix), len(matrix[0])
    holder = [-1] * m                     # holder[j]: applicant currently in job j

    def try_assign(a, tried):
        for j in range(m):
            if matrix[a][j] and not tried[j]:
                tried[j] = True
                if holder[j] == -1 or try_assign(holder[j], tried):
                    holder[j] = a         # augmenting path found: a takes job j
                    return True
        return False

    return sum(1 for a in range(n) if try_assign(a, [False] * m))

The same code is the reference solution to Place as many applicants as possible, where it is compiled and run against the tests in all six languages.

TimeO(V · E)
SpaceO(V + E)

Each applicant runs at most one search over the edges. Hopcroft–Karp finds many shortest augmenting paths per round and runs in O(E √V), worth it only for very large graphs.

König's theorem: in a bipartite graph the size of a maximum matching equals the size of a minimum vertex cover. Interviewers occasionally ask for the cover; it can be read off the final search.