workshop private

← all creations

Cut

viz · created 2026-10-01

Max-flow drawn so the backward half-edge is a visible object you can switch off, because it is the only idea in the algorithm. Without it the answer stops being a property of the graph: on one 14-node network the forward-only run returns 1 where the maximum is 6, and over 400 shuffles of the edge order it returns every integer in between while the residual run never once moves off 6. The other half is the proof. The search that fails hands back a minimum cut as a side effect — matched against brute-force enumeration of 234,880 cuts across 300 networks, it was the true minimum every time.

algorithmsinterview-prepcanvas

Ford–Fulkerson is usually taught as a loop with a hole in it: while an augmenting path exists, push flow along it. Two things in that sentence are doing enormous work and neither of them looks like it is. The first is what counts as a path — the search is allowed to travel backwards along an edge that already carries flow, undoing a decision made twenty steps ago. The second is what happens when the loop ends, which is not “we gave up” but “we have constructed a proof”.

So this is a drawing of the residual graph with a switch on it. back-edges: off is the same algorithm with the same path search and the same loop, minus the single move that makes it correct. Everything below is the difference between those two runs.

The block

Four nodes, five edges of capacity 10, maximum flow 20: ten through u, ten through v. Pick the path s → u → v → t first — which a depth-first walk does, and which is not a mistake anyone would notice making — and the three edges it uses are now full. s → v is still open and v has nowhere to go. u → t is still open and nothing can reach u. The run stops at 10, which is exactly half.

The fix is not a better path rule. It is v → u, an edge that is not in the drawing, whose capacity is 10 because that is how much flow is currently sitting on u → v. The second augmenting path is s → v, then backwards up the middle, then u → t, and the net effect is to reroute the first push rather than add to it. Nothing was undone in the sense of being thrown away; the middle edge simply stops being used, and the two cheap routes it was blocking open up.

That is the entire idea. Everything else in max-flow is bookkeeping.

The switchback, and why “close enough” is the wrong intuition

The block costs you a factor of two, which is easy to file under heuristics are usually fine. So here is the same failure with the factor made as large as you like. Six independent routes s → xᵢ → yᵢ → t, every capacity 1, maximum flow 6 — and one switchback rung from each yᵢ back up to xᵢ₊₁.

A single depth-first walk threads every rung in one path: s → x₁ → y₁ → x₂ → y₂ → … → y₆ → t. Bottleneck 1. In spending that one unit it saturates all six of the xᵢ → yᵢ edges, which are the six routes. The forward-only run returns 1 of 6, and it is not a near miss, it is a sixth. At k = 12 it returns 1 of 12. The shortfall is not bounded by a percentage; it is bounded by the size of the graph.

Whose answer is it

The sharpest version of the claim is not about size at all. Shuffle the order the search considers edges in — nothing else, same graph, same capacities, same rule — and re-run 400 times:

networkwith back-edgeswithout
the block20, every time10 or 20
the mesh20, every time13, 14, 15, 16, 17, 18, 19, 20
the switchback6, every time1, 2, 3, 4, 5, 6
bipartite matching5, every time4 or 5
the rung2000, every time1999 or 2000

The right-hand column is a lottery. The mesh returns eight different answers to the same question depending on which neighbour happened to be listed first. That is what the backward half-edge buys: not accuracy, well-definedness. With it, the number is a property of the network. Without it, the number is a property of your adjacency lists, and the program that computed it cannot tell you which.

As a control, every path rule here — shortest, first-found, widest, the adversary, and Dinic’s blocking flows — was run on all five instances and 200 random networks, 205 in total. All five rules agreed on the answer in every one.

The trap is that it usually works

Over 400 random layered networks, the forward-only run is already optimal on 342 of them. Its mean share of the maximum is 97.7%. If you shipped it you would be right 85% of the time and within a rounding error on average.

Its worst case in that sample is exactly half.

This is the uncomfortable shape of the thing: not a heuristic that fails loudly and gets replaced, but one that is nearly always right, is occasionally wrong by a factor you would never tolerate, and returns a bare integer either way.

The one-sided alarm

There is a test the forward-only run can perform on itself, and it is worth being precise about what it does and does not prove.

When it stops, look at the set S of nodes it can still reach. Every forward edge leaving S must be saturated, or it could have kept going. So the flow equals the capacity of that cut minus whatever flow crosses back into S — and that second term is exactly the amount it is leaving on the table. When it is zero, flow equals a cut’s capacity, and no flow can ever exceed any cut, so the run is provably finished.

Over 800 random networks:

runsof which suboptimal
closed with flow = its own cut4470
closed with a gap35391

Read both rows. A zero gap is a proof of optimality — 447 for 447, no exceptions, which is the min-cut theorem arriving empirically. And every one of the 91 suboptimal runs had a positive gap, so the test never missed. But 262 of the 353 alarms were raised on runs that were already optimal: a positive gap is a false alarm 74.2% of the time.

So the forward-only run can always tell when it is definitely finished, and can never tell when it is actually stuck. Which is the useless direction. The back-edge is not what detects the problem — it is the only thing that fixes one, and a detector with no repair attached is not worth much.

Same answer, different bill

Switch back-edges on and the answer is nailed down. What is still wide open is how long you take to get there, and the textbook statement of the algorithm says nothing about it at all — “find an augmenting path” is a hole the implementer fills in.

The rung: two fat sides of capacity 1000, one unit rung between them, maximum flow 2000.

ruleaugmentations
shortest path (Edmonds–Karp)2
widest first2
blocking flows (Dinic)2, in one phase
thinnest path (the adversary)2000

The adversary is not cheating. It is the same loop, obeying the same specification, picking a legal augmenting path every time — the one that runs through the rung, which alternates direction and moves one unit per round. Scale every capacity by ten and the graph does not change at all, four nodes and five edges throughout, while the bill does:

capacityshortest paththe adversary
10220
1002200
100022000
10000220000

That is the whole reason Edmonds–Karp is a named algorithm rather than a footnote: picking the shortest augmenting path replaces a bound that scales with the numbers written on the edges with one that scales with the graph. On the ten-node mesh the four rules take 8, 10, 5 and 12 augmentations — a real spread, and all four land on 20.

Worth saying plainly, though: over 300 random networks Edmonds–Karp’s O(VE) augmentation bound was never remotely approached. The worst instance used 6.5% of it, and the mean run took 4.6 augmentations. The adversary costs 1.84× the shortest-path rule in aggregate and 8× on the worst single instance. The bound is about what cannot happen, not about what does.

The proof is a side effect

The failed search is the interesting event. When no augmenting path remains, the set of nodes it managed to reach is a minimum cut — cheapest set of edges whose removal disconnects s from t — and its capacity equals the flow. You do not go looking for it. It is the wreckage of the last search.

On the mesh this is not a formality, because the cut is not guessable. Ten nodes, nineteen edges, and the minimum cut is S = {s, c, e}: two edges straight off the source, one out of c, two out of e. Five edges crossing three different depths of the drawing, summing to 20, which is the flow. There are 256 possible cuts of this network; brute force confirms this is the unique minimum, and the algorithm found it without enumerating any of them.

Across 300 random networks, every one checked both ways:

One detail that is easy to miss: 63 of the 300 networks have more than one minimum cut, and in exactly those 63 the two extremes differ. Running the reachability forward from s gives you the smallest minimum cut; running it backward from t gives the largest. Same capacity, different edges. The capacity of a minimum cut is unique; the cut is not, and if you are using the cut to decide which links to buy, that distinction is the whole decision.

Matching, for free

Set every capacity to 1 and the same machine solves bipartite matching. The instance here is deliberately not perfectly matchable — L1, L2 and L3 between them have only two partners — so the maximum matching is 5, not 6.

The question “why can’t it be 6?” is usually answered by case analysis. Here the cut answers it. The minimum cut is {L4, L5, L6, R1, R2}, five vertices, and every one of the twelve edges touches at least one of them. That is a vertex cover of size 5, so no matching can exceed 5, because each matched edge needs its own cover vertex. Matching 5, cover 5, and they are the same five objects seen from two sides — König’s theorem, delivered by the same failed search, and the unique minimum among all 4096 cuts of the network.

What’s on screen

Notes