Greedy¶
A 1/2-approximation baseline: sort every edge by cost and greedily claim the cheapest still-available pair.
Prerequisites
None — this is the one algorithm on this site that needs no background theory. See Approximation Ratio in Key Concepts for where the 1/2 bound comes from.
Why this approach¶
Every other algorithm in fastlap is exact (or, for Auction, ε-optimal to a negligible tolerance) — which always costs at least O(n²) preprocessing and typically O(n³) in the worst case. Sometimes that's more than you need: if you're gating candidates upstream (e.g. only keeping edges under a distance threshold) or working under a tight latency budget, a fast approximate matching can be good enough, or serve as a cheap sanity baseline to compare an exact solver against.
How it works¶
- Collect every finite
(row, col, cost)triple in the matrix. - Sort them by cost, ascending.
- Walk the sorted list once: claim a pair if both its row and its column are still unclaimed; skip it otherwise.
There's no backtracking and no reconsideration — once a row or column is claimed, it's claimed for good, even if a later (more expensive) pairing would have unlocked a much cheaper solution elsewhere. This greedy, no-lookahead structure is exactly what bounds it to a 1/2-approximation: the total cost is never more than twice the true optimum.
Pseudocode¶
function GREEDY(cost, n, m):
edges = [(i, j, cost[i][j]) for every finite cell]
sort edges by cost, ascending
row_used, col_used = false, false
for (i, j, c) in edges:
if not row_used[i] and not col_used[j]:
assign i -> j
row_used[i] = col_used[j] = true
return assignment, sum of assigned costs
Complexity¶
| Cost | |
|---|---|
| Time | O(n² log n) — building the edge list is O(n²), sorting it dominates at O(n² log n²) = O(n² log n), and the single greedy pass is O(n²) |
| Space | O(n²) — the edge list holds every finite cell; row_used/col_used are O(n) |
Worked example¶
fastlap's own test suite (src/lap/greedy.rs) verifies this exact case:
graph LR
subgraph Rows
R0((row 0))
R1((row 1))
R2((row 2))
end
subgraph Columns
C0((col 0))
C1((col 1))
C2((col 2))
end
R0 -->|1| C0
R0 -->|5| C1
R0 -->|9| C2
R1 -->|8| C0
R1 -->|2| C1
R1 -->|6| C2
R2 -->|4| C0
R2 -->|7| C1
R2 -->|3| C2
linkStyle 0 stroke:#2e7d32,stroke-width:3px
linkStyle 4 stroke:#2e7d32,stroke-width:3px
linkStyle 8 stroke:#2e7d32,stroke-width:3px
Sorted edges: (0,0,1), (1,1,2), (2,2,3), (2,0,4), (0,1,5), (1,2,6), (2,1,7), (1,0,8), (0,2,9).
Greedy pass:
(0,0,1)— both free → claim. Row 0 → col 0.(1,1,2)— both free → claim. Row 1 → col 1.(2,2,3)— both free → claim. Row 2 → col 2.- Every remaining edge involves an already-claimed row or column — all skipped.
Result: row 0→0, row 1→1, row 2→2, cost 1 + 2 + 3 = 6. In this particular example, the greedy result happens to coincide with the true optimum — the three cheapest cells in the whole matrix happen to form a valid matching — but that's not guaranteed in general; greedy only guarantees staying within 2× of optimal.
import fastlap
cost = [[1, 5, 9], [8, 2, 6], [4, 7, 3]]
total, rows, cols = fastlap.solve_lap(cost, algorithm="greedy")
print(total, rows) # 6.0 [0, 1, 2]
Common pitfalls¶
Greedy can be arbitrarily bad on adversarial input
The 1/2-approximation bound is tight — it's not a pessimistic worst case that never actually happens. Consider:
Sorted edges: (0,0,1), (1,0,1.5), (0,1,2), (1,1,100). Greedy claims (0,0) first (cost 1), which blocks (1,0) — the actually cheap way to route row 1. Row 1 is left with only column 1, at cost 100. Greedy's total: 1 + 100 = 101. True optimum: row 0→col 1, row 1→col 0, total 2 + 1.5 = 3.5. Greedy's no-lookahead, no-backtracking structure has no way to see that coming.
In general: the earlier a cheap edge appears in sort order, the more it can foreclose a much better global structure. This is exactly why every other algorithm on this site pays for some form of lookahead (augmenting paths, dual reasoning, or simplex pivots) instead.
Ties in the sorted edge list are broken by whatever order the sort happens to leave them in (not by any preference for one solution over another) — don't rely on greedy's output being deterministic-by-value across ties in general-purpose code, even though fastlap's sort is itself deterministic for a given input.
When to use it¶
Use "greedy" only when you explicitly want speed over optimality — a rough approximate matching under a strict time budget, or a fast baseline to compare an exact solver's runtime against. For anything where the assignment itself matters (tracking, scheduling, resource allocation), use one of the ten exact (or ε-optimal) algorithms instead — LAPJV by default.
Not exact
Unlike every other algorithm on this page's family of siblings, greedy does not guarantee the optimal assignment — only a cost at most 2× the optimum.