Hungarian Algorithm Explained (Step-by-Step)

Last updated: β€’ Sources β€’ Methodology

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.

What this guide covers (lecture level):
  • 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: 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)

  1. Row reduction: subtract each row minimum from that row.
  2. Column reduction: subtract each column minimum from that column.
  3. 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.)
  4. Cover all zeros with a minimum number of lines (rows/columns).
  5. If the number of lines equals n, you can form an optimal assignment from independent zeros. Otherwise:
    • let k = smallest uncovered value,
    • subtract k from all uncovered entries,
    • add k to all entries at intersections of two lines,
    • repeat from step 3.
The minimum-line-cover step connects to matching theory (and why Hungarian is also described as a matching algorithm). See bipartite matching & assignment.

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

  1. Initial starring: scan the matrix; star a zero if its row and column contain no other starred zero yet.
  2. Cover columns that contain a starred zero. If all n columns are covered, the starred zeros ARE the optimal assignment β€” stop here.
  3. 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.
  4. Numeric adjustment (only reached if Step 3 found no uncovered zero): let k = the smallest value among all uncovered cells. Subtract k from every uncovered cell; add k to 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.
  5. Build and apply the augmenting path: starting from the primed zero Z0 found in Step 3, alternate:
    • find the starred zero in Z0‘s column (if any) β†’ call it Z1,
    • find the primed zero in Z1‘s row β†’ call it Z2,
    • repeat until you reach a primed zero whose column has no starred zero.
    Then unstar every starred zero in this chain and star every primed zero in it. Erase all remaining primes, remove all line covers, and return to Step 2 (you now have one more starred zero than before).

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
W4J145
W2J355
W3J295
W1J480

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.

Verification: checking all 24 possible worker-job assignments for this 4Γ—4 matrix confirms 275 is the global minimum and that exactly these two assignments achieve it β€” nothing scores lower. For more fully finished matrices, see assignment problem examples.
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

  1. Practice more matrices: assignment problem examples.
  2. Unbalanced/dummy modeling: unbalanced assignment.
  3. Graph interpretation: bipartite matching & assignment.
  4. 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.

Related guides

Sources
  1. Kuhn (1955) β€” The Hungarian method for the assignment problem
  2. Munkres (1957) β€” Algorithms for the Assignment and Transportation Problems (SIAM)
  3. Wikipedia β€” Hungarian algorithm (overview)
  4. Wikipedia β€” Assignment problem (overview)