Hungarian Algorithm (KuhnβMunkres), Step-by-Step
The Hungarian algorithm (also called the KuhnβMunkres algorithm) is a polynomial-time method (commonly O(nΒ³)) for the assignment problem (a.k.a. the linear assignment problem): choose exactly one job per worker and one worker per job to minimize total cost.
Jump to: steps Β· star/prime method Β· pseudocode Β· worked examples Β· sources
If you want the βproblem familyβ context first, see assignment vs transportation problem. For many solved matrices, see assignment problem examples.
- The assignment model (matrix + constraints) and what βone-to-oneβ means
- Row/column reduction, covering zeros, and the adjustment step
- The star/prime method for systematically finding independent zeros, including augmenting paths
- A dual (potentials) interpretation via primalβdual ideas
- Unbalanced assignment (dummy rows/cols) via unbalanced assignment
On this page
- Quick takeaway
- Assignment problem model (matrix + constraints)
- Core idea: reduced costs and zero structure
- Hungarian algorithm steps (full)
- Star/prime method (finding independent zeros)
- Pseudocode
- Worked examples
- Unbalanced assignment using dummy rows/columns
- Common variants (maximization, forbidden pairs)
- Questions people ask
- What to do next
Quick takeaway: Hungarian algorithm repeatedly transforms the cost matrix without changing which assignments are optimal. It creates many zeros (tight reduced costs), then finds a set of independent zeros (no two share a row or a column). If there are not enough independent zeros, it performs a structured adjustment step and repeats.
Assignment problem model (matrix + constraints)
Given an nΓn cost matrix C = [c(i,j)], choose exactly one entry in each row and each column to minimize total cost.
This is the classic linear assignment problem.
Binary variable form
minimize Ξ£_i Ξ£_j c(i,j) x(i,j)
subject to Ξ£_j x(i,j) = 1 for each i
Ξ£_i x(i,j) = 1 for each j
x(i,j) β {0,1}
This is closely related to linear programming. The assignment structure is also tied to the graph view: bipartite matching & assignment.
How does the assignment problem compare to related problems?
A quick comparison helps clarify when Hungarian algorithm applies versus other classic OR methods:
| Problem | Structure | Typical method |
|---|---|---|
| Assignment problem | One-to-one, equal sides (or squared via dummies) | Hungarian algorithm (KuhnβMunkres) |
| Bipartite matching | Many-to-many possible, unweighted or weighted | HopcroftβKarp / augmenting-path search |
| Transportation problem | General supplies β demands | Transportation simplex / MODI method |
See also: assignment vs transportation problem and bipartite matching & assignment.
Core idea: reduced costs, dual potentials, and zeros
A lecture-level way to understand Hungarian is through the LP dual (often called potentials):
choose row potentials u_i and column potentials v_j so that:
u_i + v_j β€ c(i,j) for all i,j
The quantity c(i,j) - u_i - v_j is a reduced cost (nonnegative).
Cells where c(i,j) - u_i - v_j = 0 are βtightβ and become zeros in the reduced matrix.
Hungarian algorithm manipulates the matrix exactly by updating these potentialsβcreating zeros and searching for a perfect matching among them.
If you want the general duality story, see primalβdual relationship in LP.
Why “Hungarian”? Harold Kuhn named the method in honor of two Hungarian mathematicians, DΓ©nes KΕnig and JenΕ EgervΓ‘ry, whose earlier theorems on bipartite graphs (KΓΆnig’s theorem) are the theoretical foundation for why the algorithm’s zero-cover step correctly identifies an optimal assignment.
Hungarian algorithm steps (full)
- Row reduction: subtract each row minimum from that row.
- Column reduction: subtract each column minimum from that column.
- Select independent zeros: try to pick zeros so that no two chosen zeros share a row or a column. (That selection corresponds to a valid one-to-one assignment.)
- Cover all zeros with a minimum number of lines (rows/columns).
-
If the number of lines equals
n, you can form an optimal assignment from independent zeros. Otherwise:- let
k= smallest uncovered value, - subtract
kfrom all uncovered entries, - add
kto all entries at intersections of two lines, - repeat from step 3.
- let
The star/prime method: finding independent zeros systematically
Step 3 above (“select independent zeros”) is easy to eyeball on a 3Γ3 matrix but becomes error-prone by hand once you reach 5Γ5 or larger. The star/prime method (the bookkeeping scheme inside the classical Munkres algorithm) gives you a deterministic procedure that never requires guessing.
Definitions
- Starred zero: a zero that is provisionally part of the assignment. No two starred zeros ever share a row or column.
- Primed zero: a zero that has been “flagged” while searching for a way to extend the current set of stars. Primes are temporary working marks, not part of the final assignment.
- Covered row/column: a row or column temporarily excluded from the search for uncovered zeros, using the line-cover idea from Step 4.
- Augmenting path: an alternating chain of primed and starred zeros that, when “flipped” (primes become stars, stars become unstarred), increases the number of independent zeros by exactly one.
The full procedure
- Initial starring: scan the matrix; star a zero if its row and column contain no other starred zero yet.
-
Cover columns that contain a starred zero.
If all
ncolumns are covered, the starred zeros ARE the optimal assignment β stop here. -
Search for an uncovered zero.
- If none exists, go to Step 4 (numeric adjustment) using the current covers, then return to Step 2.
- If found, prime it.
- If its row already has a starred zero: cover that row, uncover that star’s column, and keep searching for uncovered zeros (repeat Step 3).
- If its row has no starred zero: this primed zero is the start of an augmenting path β go to Step 5.
-
Numeric adjustment (only reached if Step 3 found no uncovered zero): let
k= the smallest value among all uncovered cells. Subtractkfrom every uncovered cell; addkto every cell covered twice (at a covered-row/covered-column intersection); leave singly-covered cells unchanged. This creates at least one new uncovered zero. Return to Step 2. -
Build and apply the augmenting path: starting from the primed zero
Z0found in Step 3, alternate:- find the starred zero in
Z0‘s column (if any) β call itZ1, - find the primed zero in
Z1‘s row β call itZ2, - repeat until you reach a primed zero whose column has no starred zero.
- find the starred zero in
Mini-example: watching an augmenting path happen
Here is a small reduced matrix (already row/column reduced) chosen specifically to show a case where the greedy initial starring picks the wrong zeros first, and an augmenting path is needed to fix it.
J1 J2 J3 W1 0 0 4 W2 0 3 5 W3 0 6 0
Step 1 β Initial starring (scanning row by row, left to right):
- Row W1: zeros at J1 and J2 β star W1βJ1 (first one found).
- Row W2: zero at J1 β column J1 already starred β skip.
- Row W3: zeros at J1 and J3 β J1 taken β star W3βJ3.
Stars so far: W1βJ1, W3βJ3 (only 2, need 3). Cover columns J1 and J3. Column J2 is uncovered.
Step 2 β First uncovered zero
Column J2 is uncovered. Scanning it: W1βJ2 = 0. Prime it. Row W1 already has a starred zero (W1βJ1) β cover row W1, uncover column J1.
Now covered: row W1, column J3. Uncovered: rows W2, W3; columns J1, J2.
Step 3 β Second uncovered zero
Checking rows W2 and W3 in columns J1 and J2: W2βJ1 = 0 (row W2 uncovered, column J1 uncovered). Prime it. Row W2 has no starred zero β this is the start of an augmenting path.
Step 4 β Trace the augmenting path
Z0= W2βJ1 (just primed)Z1= starred zero in column J1 β W1βJ1 (still starred β uncovering a column earlier does not remove the star, it only removes the temporary cover line)Z2= primed zero in row W1 β W1βJ2- Column J2 has no starred zero β path ends at
Z2
Path found: W2βJ1 β W1βJ1 β W1βJ2.
Step 5 β Flip the path
Unstar W1βJ1. Star W2βJ1 and W1βJ2 (both were primed).
New star set: W1βJ2, W2βJ1, W3βJ3 β 3 stars, covering all 3 columns. Done.
Result: the final assignment is W1βJ2, W2βJ1, W3βJ3 β found by systematically extending the matching by one, rather than by trial and error. This exact mechanic (star β prime β trace β flip) is what runs, matrix after matrix, inside a full Hungarian algorithm solve.
Pseudocode
Level 1 β high-level overview
for each row: subtract the row minimum
for each column: subtract the column minimum
repeat:
find a maximum matching using only zero-cost cells
if matching size == n:
return matching // optimal assignment found
find the minimum number of lines covering all zeros
k = smallest value not covered by any line
subtract k from every uncovered cell
add k to every cell covered by two lines
// singly covered cells stay unchanged
Level 2 β star/prime bookkeeping (what “find a maximum matching” actually does)
function hungarian(cost):
n = size of cost matrix
subtract row minima, then column minima // as above
stars = empty nΓn boolean grid
primes = empty nΓn boolean grid
row_covered, col_covered = all false
# initial starring
for each zero cell (i, j):
if no star in row i and no star in column j:
star(i, j)
loop:
col_covered[j] = true for every column j containing a star
if count(col_covered) == n:
return read_assignment(stars)
(i, j) = find an uncovered zero cell // not in a covered row or column
if no such cell exists:
k = min value among all uncovered cells
add k to cells covered by both a row and a column line
subtract k from cells covered by neither
continue loop // a new uncovered zero now exists
prime(i, j)
if row i already contains a starred zero at column j2:
row_covered[i] = true
col_covered[j2] = false
continue loop // keep searching for uncovered zeros
else:
path = build_augmenting_path(i, j)
for (r, c) in path: toggle_star(r, c) // starsβprimes flip along the path
clear all primes
row_covered, col_covered = all false
continue loop // re-cover columns, one more star exists now
function build_augmenting_path(start_i, start_j):
path = [(start_i, start_j)] // Z0: primed
col = start_j
loop:
row = row_of_starred_zero_in_column(col)
if row is None:
return path // path ends on a primed zero
path.append((row, col)) // Z1, Z3, ... : starred
col = column_of_primed_zero_in_row(row)
path.append((row, col)) // Z2, Z4, ... : primed
Why this matters for performance: a naive re-scan of the whole matrix for the smallest
uncovered value at every adjustment step costs O(nΒ²) per adjustment and can require up to O(n) adjustments,
giving O(nΒ³) in a straightforward implementation β or worse if the “find a maximum matching” step is also
recomputed from scratch. Efficient implementations (including SciPy’s linear_sum_assignment)
avoid repeated full re-scans by maintaining a running slack array (the smallest reduced cost
currently reachable in each uncovered column), updating it incrementally as covers change rather than
recomputing it. That bookkeeping detail is what keeps the algorithm reliably at O(nΒ³) rather than O(nβ΄).
Worked examples
Worked example
Example 1 (4Γ4, minimization): full star/prime solve across two iterations
Cost matrix (minimize):
J1 J2 J3 J4 W1 90 75 75 80 W2 35 85 55 65 W3 125 95 90 105 W4 45 110 95 115
Step 1 β Row reduction
- Row W1 min=75 β (15, 0, 0, 5)
- Row W2 min=35 β (0, 50, 20, 30)
- Row W3 min=90 β (35, 5, 0, 15)
- Row W4 min=45 β (0, 65, 50, 70)
J1 J2 J3 J4 W1 15 0 0 5 W2 0 50 20 30 W3 35 5 0 15 W4 0 65 50 70
Step 2 β Column reduction
Only column J4 has a nonzero minimum (5); subtract it.
J1 J2 J3 J4 W1 15 0 0 0 W2 0 50 20 25 W3 35 5 0 10 W4 0 65 50 65
Iteration 1 β Star, cover, and check
Initial starring (row by row, first available zero):
- W1: zeros at J2, J3, J4 β star W1βJ2
- W2: zero at J1 β star W2βJ1
- W3: zero at J3 β star W3βJ3
- W4: zero at J1 β column J1 already starred β no star for W4
3 stars found (need 4). Cover columns J1, J2, J3. Column J4 is uncovered.
Searching column J4 for an uncovered zero: W1βJ4 = 0. Prime it. Row W1 already has a starred zero (W1βJ2), so: cover row W1, uncover column J2.
Now covered: row W1, columns J1, J3. Uncovered: rows W2, W3, W4; columns J2, J4.
Searching rows W2, W3, W4 in columns J2, J4 for a zero: none exist (values are 50, 25, 5, 10, 65, 65 β all nonzero). No uncovered zero found β proceed to numeric adjustment.
Iteration 1 β Adjustment
Uncovered cells are rows W2, W3, W4 crossed with columns J2, J4. Their values: 50, 25, 5, 10, 65, 65. Smallest uncovered value: k = 5 (at W3βJ2).
- Subtract 5 from every uncovered cell (rows W2/W3/W4 Γ columns J2/J4)
- Add 5 to every doubly-covered cell (row W1 β© columns J1, J3) β W1βJ1 and W1βJ3
- Leave singly-covered cells unchanged
J1 J2 J3 J4 W1 20 0 5 0 W2 0 45 20 20 W3 35 0 0 5 W4 0 60 50 60
Iteration 2 β Re-star and check
Re-starring from scratch on the updated matrix:
- W1: zeros at J2, J4 β star W1βJ2
- W2: zero at J1 β star W2βJ1
- W3: zeros at J2, J3 β J2 taken β star W3βJ3
- W4: zero at J1 β column J1 already starred β no star for W4
Still 3 stars (need 4). Cover columns J1, J2, J3. Uncovered: column J4.
Searching column J4: W1βJ4 = 0. Prime it. Row W1 has a starred zero (W1βJ2) β cover row W1, uncover column J2.
Now covered: row W1, columns J1, J3. Uncovered: rows W2, W3, W4; columns J2, J4.
Searching rows W2, W3, W4 in columns J2, J4: W3βJ2 = 0. Prime it. Row W3 has a starred zero (W3βJ3) β cover row W3, uncover column J3.
Now covered: rows W1, W3; column J1. Uncovered: rows W2, W4; columns J2, J3, J4.
Searching rows W2, W4 in columns J2, J3, J4: values are 45, 20, 20 (W2) and 60, 50, 60 (W4) β no zero. No uncovered zero found β adjust again.
Iteration 2 β Adjustment
Uncovered cells: rows W2, W4 crossed with columns J2, J3, J4 β values 45, 20, 20, 60, 50, 60. Smallest: k = 20.
- Subtract 20 from every uncovered cell (rows W2/W4 Γ columns J2/J3/J4)
- Add 20 to doubly-covered cells (rows W1, W3 β© column J1) β W1βJ1 and W3βJ1
- Leave singly-covered cells unchanged
J1 J2 J3 J4 W1 40 0 5 0 W2 0 25 0 0 W3 55 0 0 5 W4 0 40 30 40
Final independent zeros
Zero positions now: W1 (J2, J4), W2 (J1, J3, J4), W3 (J2, J3), W4 (J1) only.
W4 has only one zero (J1), so it must take J1. That forces the rest: W2 must use J3 or J4 (not J1); W3 must use J2 or J3; W1 must use J2 or J4. One valid selection: W4βJ1, W2βJ3, W3βJ2, W1βJ4 β check: columns J1, J3, J2, J4 are all distinct and all four cells are zeros in the current matrix. This is a complete, valid assignment.
Final answer (translated back to the original cost matrix)
| Worker | Job | Original cost |
|---|---|---|
| W4 | J1 | 45 |
| W2 | J3 | 55 |
| W3 | J2 | 95 |
| W1 | J4 | 80 |
Minimum total cost = 45 + 55 + 95 + 80 = 275.
There is a second, equally optimal assignment (the matrix has a tie): W4βJ1 (45), W1βJ2 (75), W3βJ3 (90), W2βJ4 (65), which also totals 275. Both are valid β whenever multiple independent-zero selections exist in the final matrix, multiple optima exist.
Worked example
Example 2 (3Γ3): a case solved in a single starring pass
Cost matrix:
J1 J2 J3 W1 9 2 7 W2 6 4 3 W3 5 8 1
Row reduction (minima 2, 3, 1):
J1 J2 J3 W1 7 0 5 W2 3 1 0 W3 4 7 0
Column reduction (only J1 has a nonzero minimum, 3):
J1 J2 J3 W1 4 0 5 W2 0 1 0 W3 1 7 0
Starring row by row: W1 β J2 (only zero). W2 β zeros at J1 and J3, star J1 (first found). W3 β zero at J3 (J1 is taken, but J3 is free) β star J3. Three stars cover all three columns immediately β no priming needed this time.
Final answer: W1βJ2 (2), W2βJ1 (6), W3βJ3 (1). Total cost = 9.
Verified by checking all 6 permutations of this 3Γ3 matrix β 9 is the unique global minimum (no ties here, which is a useful contrast to Example 1 above, where ties existed).
Worked example
Example 3 (maximization): profit matrix solved end-to-end
For maximization (profit) assignment, convert profit to an equivalent minimization cost matrix using
cost(i,j) = Pmax β profit(i,j), then apply Hungarian normally.
Profit matrix:
J1 J2 J3 W1 8 6 5 W2 6 9 7 W3 5 7 10
Pmax = 10. Converted cost matrix (10 β profit):
J1 J2 J3 W1 2 4 5 W2 4 1 3 W3 5 3 0
Row reduction (minima 2, 1, 0):
J1 J2 J3 W1 0 2 3 W2 3 0 2 W3 5 3 0
Column minima are already all 0 β no column reduction needed. Zeros fall neatly on the diagonal (W1βJ1, W2βJ2, W3βJ3), so all three star immediately with no conflicts.
Final answer: W1βJ1 (profit 8), W2βJ2 (profit 9), W3βJ3 (profit 10). Total profit = 27.
Verified against all 6 permutations β 27 is the maximum achievable profit for this matrix.
Worked example
Example 4 (unbalanced): 2 workers, 3 jobs β solved with a dummy row
When jobs outnumber workers (or vice versa), add a dummy row or column with zero cost so the matrix becomes square, then solve normally. Full conceptual guide: unbalanced assignment problem.
Cost matrix (2 workers, 3 jobs):
J1 J2 J3 W1 4 7 3 W2 6 5 8
Add a dummy worker W3(dummy) with cost 0 for every job:
J1 J2 J3 W1 4 7 3 W2 6 5 8 W3(dummy) 0 0 0
Row reduction (minima 3, 5, 0) then column reduction (all column minima already 0):
J1 J2 J3 W1 1 4 0 W2 1 0 3 W3(dummy) 0 0 0
Starring: W1 β J3 (only zero). W2 β J2 (only zero). W3(dummy) β zeros at J1 and J2, but J2 is taken β star J1. Three stars cover all columns β done.
Interpretation: the dummy worker “takes” job J1, meaning J1 is the job that goes unfulfilled.
Final answer: W1βJ3 (3), W2βJ2 (5), J1 left unassigned. Total real cost = 8.
Verified against all 6 possible ways to assign 2 workers to 3 jobs (leaving one job undone each time) β 8 is the minimum.
Unbalanced assignment using dummy rows/columns
When the matrix is rectangular (e.g., 3 workers and 4 jobs), convert it to square by adding a dummy worker or dummy job. The dummy represents βunused workerβ or βunfilled job,β depending on which side is short. Example 4 above walks through this fully.
Full guide with examples: Unbalanced assignment (dummy rows/cols).
Common variants (maximization, forbidden pairs)
Maximization
How to solve maximum profit assignment
Convert to minimization using a constant shift. This preserves the identity of optimal assignments because adding/subtracting the same constant per row/column doesnβt change the argmin structure. See Example 3 above for a full numeric walkthrough.
Typical conversion: cost(i,j) = Pmax - profit(i,j) (where Pmax is the largest profit in the matrix).
Forbidden pairs
What if some worker-job assignments are not allowed?
A common modeling tactic is to assign a very large penalty cost to forbidden cells so they are never chosen. If the βforbiddenβ structure is important, consider modeling as an ILP and solving with an ILP/MILP solver: integer linear programming.
Avoid absurdly huge penalties (numerical issues). For solver numerics concepts, see integrality tolerance.
Questions people ask
Questions people ask
Why do row and column reductions not change the optimal assignment?
Subtracting a constant from every element of a row subtracts the same constant from the total cost of every assignment (since every feasible assignment uses exactly one cell from that row). The argmin stays the same.
Questions people ask
What are βindependent zerosβ in the Hungarian algorithm?
Independent zeros are zeros chosen so that no two are in the same row or column. Picking n independent zeros gives a valid one-to-one assignment.
Questions people ask
What is an augmenting path in the Hungarian algorithm?
It’s an alternating chain of primed and starred zeros, built when the current set of starred zeros can’t be directly extended. Flipping every zero along the path (stars become unstarred, primes become starred) increases the number of independent zeros by exactly one, without breaking the “no shared row/column” rule. See the worked mini-example above for a full trace.
Questions people ask
What does βcover all zeros with minimum number of linesβ mean?
You draw the fewest horizontal/vertical lines so every zero is crossed by at least one line.
If that minimum number equals n, there exists a full set of independent zeros (an optimal assignment can be built).
Questions people ask
Why does the adjustment step work?
Subtracting the smallest uncovered value creates new zeros (new tight reduced costs) without violating nonnegativity of reduced costs. From a dual viewpoint, it updates row/column potentials while keeping feasibility.
Questions people ask
Is Hungarian algorithm the same as KuhnβMunkres?
In most OR references, βHungarianβ and βKuhnβMunkresβ refer to the same core approach for the linear assignment problem.
Questions people ask
Can Hungarian solve a rectangular (unbalanced) matrix directly?
Standard Hungarian is described for square matrices. For rectangular cases, add dummy rows/columns to make it square: unbalanced assignment.
Questions people ask
What is the time complexity of the Hungarian algorithm?
It is commonly given as O(nΒ³) for an nΓn assignment matrix (depending on implementation details).
If you need an authoritative citation, see the sources below.
Questions people ask
Can there be multiple optimal assignments?
Yes. If there are alternative independent-zero selections leading to the same cost, you have multiple optima. Related concept: multiple optimal solutions.
Questions people ask
When should I use transportation methods instead of Hungarian?
If you have general supplies and demands (not strictly one-to-one), your model is closer to transportation. See assignment vs transportation problem.
Questions people ask
How do I implement Hungarian algorithm in Python?
Use SciPyβs assignment solver: Hungarian algorithm in Python (SciPy).
What to do next
- Practice more matrices: assignment problem examples.
- Unbalanced/dummy modeling: unbalanced assignment.
- Graph interpretation: bipartite matching & assignment.
- Python implementation: Hungarian algorithm in Python.
Disclaimer
This guide is for educational and informational purposes. Real assignment/scheduling problems may include constraints not captured by the one-to-one assignment model (skills, shifts, precedence, fairness, overtime, multi-period requirements). In such cases, you may need a broader optimization model (often ILP/MILP) rather than Hungarian alone. Always validate assumptions and results using appropriate domain constraints and, when necessary, a production-grade solver. Read the full disclaimer.