Max-flow min-cut theorem. Ford-Fulkerson algorithm.
Multi-session curriculum - substantial prior knowledge and complex material. Use mastery gates and deliberate practice.
If you’ve ever tried to route “as much as possible” through a bottlenecked system—traffic through streets, data through links, jobs through machines—you’ve met network flow. The surprising part is that the answer to “how much can we push?” is exactly equal to a seemingly different object: the capacity of a best “barrier” (a cut) separating source from sink.
A feasible s–t flow assigns values f(u,v) on directed edges so that 0 ≤ f(u,v) ≤ c(u,v) and every intermediate vertex conserves flow. The Ford–Fulkerson method repeatedly finds an augmenting path in the residual network (edges with remaining capacity, plus backward edges that allow undoing flow) and augments along it. When no augmenting path exists, the flow is maximum and the set of vertices reachable from s in the residual graph defines a minimum s–t cut, proving max-flow = min-cut.
Many optimization problems on graphs are about routes (shortest path), orderings (topological constraints), or structures (spanning trees). Network flow is different: it is about simultaneous movement through many edges at once, where each edge has a limited capacity.
Think of a directed graph as a pipe network:
The core question:
What is the maximum amount of flow that can be sent from to without violating edge capacities?
A flow network is a directed graph with:
We allow the convention that if , then .
A flow is a function (often only nonzero on edges), interpreted as = amount sent on edge .
A flow is feasible if it satisfies:
1) Capacity constraints (can’t exceed the pipe):
2) Flow conservation (what goes in must go out) for every intermediate vertex :
Let incoming flow to be and outgoing be . Then
Equivalently, net flow at is zero.
3) Value of the flow (objective):
The amount successfully sent from to is
In many treatments we assume no edges enter and no edges leave , making this simply .
Max flow is a linear program:
That matters because it hints at:
But the network-flow viewpoint gives more than “solve an LP”: it gives a combinatorial algorithm and a geometric intuition.
A cut feels like a “barrier” rather than a “routing.” Yet it pins down the answer.
An $s$–$t$ cut is a partition of vertices into two sets such that:
The capacity of the cut is the total capacity of edges going from to :
Intuition: if you draw a boundary around , the only way flow can go from to is to cross from to . Those crossing edges limit the total possible flow.
We will later prove:
That equality is the Max-Flow Min-Cut Theorem—the central result of this node.
Suppose you have some feasible flow . You want to increase it.
If an edge has unused capacity, you can push more forward.
But you also want the ability to change your mind: if later you discover that flow you sent earlier blocks a better routing, you’d like to pull some flow back and reroute it elsewhere.
Residual networks encode both possibilities:
This is what makes Ford–Fulkerson work.
Given a capacity and current flow , the forward residual capacity is:
If , you can increase .
Now the crucial twist: if , you can decrease it by sending flow in the reverse direction. That creates a backward edge in the residual graph with residual capacity:
This is not a real physical edge in the original network; it represents “canceling” previously sent flow.
The residual network contains all directed edges with positive residual capacity:
Each such edge has capacity .
An augmenting path is any directed path from to in the residual network .
If you can find one, you can increase the total flow value.
Let be an augmenting path. The maximum amount you can push through it is limited by the smallest residual capacity along it:
This is called the bottleneck of the path.
For each edge on the path :
This preserves feasibility:
The picture below shows one original edge and the residual edges it induces.
<svg xmlns="http://www.w3.org/2000/svg" width="820" height="230" viewBox="0 0 820 230" role="img" aria-label="Residual network illustration showing forward residual capacity and backward residual edge for canceling flow">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L10,3 L0,6 Z" fill="#222" />
</marker>
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L10,3 L0,6 Z" fill="#1f77b4" />
</marker>
<marker id="arrowRed" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L10,3 L0,6 Z" fill="#d62728" />
</marker>
</defs>
<!-- Nodes -->
<circle cx="170" cy="115" r="28" fill="#fff" stroke="#222" stroke-width="2" />
<text x="170" y="121" text-anchor="middle" font-family="sans-serif" font-size="16">u</text>
<circle cx="650" cy="115" r="28" fill="#fff" stroke="#222" stroke-width="2" />
<text x="650" y="121" text-anchor="middle" font-family="sans-serif" font-size="16">v</text>
<!-- Original edge -->
<line x1="198" y1="115" x2="622" y2="115" stroke="#222" stroke-width="3" marker-end="url(#arrow)" />
<text x="410" y="90" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#222">original edge (u→v)</text>
<text x="410" y="136" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#222">capacity c(u,v)=10, current flow f(u,v)=6</text>
<!-- Residual forward -->
<line x1="210" y1="55" x2="610" y2="55" stroke="#1f77b4" stroke-width="3" marker-end="url(#arrowBlue)" />
<text x="410" y="40" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#1f77b4">forward residual: c_f(u,v)=c-f=4</text>
<!-- Residual backward -->
<line x1="610" y1="175" x2="210" y2="175" stroke="#d62728" stroke-width="3" marker-end="url(#arrowRed)" />
<text x="410" y="200" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#d62728">backward residual: c_f(v,u)=f=6 (can cancel)</text>
<text x="410" y="18" text-anchor="middle" font-family="sans-serif" font-size="16" fill="#222">Residual edges represent “can add” and “can undo”</text>
</svg>Residual edges are often the hardest part to internalize. Two reminders help:
1) Backward residual edges do not violate the original graph direction. They are bookkeeping that says: “you may reduce the earlier decision.”
2) Augmenting along a residual path may mix forward and backward edges. That corresponds to a rerouting: some flow gets pushed into a new corridor, and some earlier corridor gets partially emptied.
Let be an path and augment by .
So:
That’s the engine of Ford–Fulkerson.
We want maximum flow. Instead of guessing a final flow, we:
1) start with zero flow
2) repeatedly find a way to push more (augmenting path)
3) stop when no improvement is possible
This is a classic “local improvement” strategy, except the residual network is designed so that “no local improvement” actually implies “global optimum.”
Initialize for all edges.
Repeat:
Stop when no augmenting path exists.
This is a subtle point, and it’s important at difficulty 4.
So, for guaranteed polynomial-time behavior, we specify a strategy for picking augmenting paths.
Edmonds–Karp is Ford–Fulkerson where the augmenting path is chosen as the shortest path in number of edges in the residual graph (use BFS).
Key facts:
Even if you don’t memorize the proof, the intuition is useful:
| Approach | How augmenting path is chosen | Termination guarantee | Typical bound | ||
|---|---|---|---|---|---|
| Ford–Fulkerson (generic) | Any augmenting path | Yes for integer/rational capacities; not guaranteed for arbitrary reals | $O(E \cdot | f^* | )$ augmentations in integer case (pseudo-polynomial) |
| Edmonds–Karp | BFS shortest (#edges) | Yes | |||
| Dinic (preview) | Level graph + blocking flow | Yes | general; faster in special cases |
When implementing, you almost always store:
Even if the original graph has only forward edges, the residual graph will contain backward edges during the run.
If all capacities are integers, maximum flow has an optimal solution with integer flows.
Reason (informal but accurate):
This is extremely powerful: it turns many “counting” problems into flow problems (matchings, disjoint paths, bipartite assignment variants).
Ford–Fulkerson gives you a flow. But how do you know it’s optimal?
Max-Flow Min-Cut gives a crisp certificate:
So you get both:
Take any feasible flow and any cut .
Consider net flow leaving :
Because every vertex in except possibly has flow conservation, all internal cancellations happen, and the expression collapses to (the only net source inside is ).
More directly, one can show:
Now use bounds:
So:
This implies:
So the max flow is always ≤ min cut capacity.
Assume Ford–Fulkerson has stopped: there is no path in residual graph .
Let be the set of vertices reachable from in (including ). Let .
Because is not reachable, we have . Thus is a valid cut.
Now consider any original edge with and .
Claim: this edge must be saturated:
Why? If it were not saturated, then forward residual capacity would be positive:
meaning would appear in , and then would be reachable from (and from ), contradicting .
So every edge from to is saturated.
Also consider any original edge from to (opposite direction). Claim: it must carry zero flow:
Why? If , then the backward residual edge would have positive residual capacity , making reachable from and hence from , again contradicting .
Compute using the cut identity:
But we just established:
So:
Thus the current flow equals the capacity of this cut.
Combine with Step 1:
Therefore is maximum and is a minimum cut:
This diagram shows a residual graph situation at termination: vertices reachable from are shaded as ; is in . All edges from to are saturated (no forward residual capacity), so there is no crossing residual edge.
<svg xmlns="http://www.w3.org/2000/svg" width="880" height="320" viewBox="0 0 880 320" role="img" aria-label="Cut from reachable set in residual network: S is reachable from s, T contains t, and edges from S to T are saturated">
<defs>
<marker id="arr" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L10,3 L0,6 Z" fill="#222" />
</marker>
</defs>
<!-- Background regions -->
<rect x="20" y="40" width="400" height="250" fill="#e8f4ff" stroke="#1f77b4" stroke-width="2"/>
<rect x="460" y="40" width="400" height="250" fill="#fff0f0" stroke="#d62728" stroke-width="2"/>
<text x="220" y="30" text-anchor="middle" font-family="sans-serif" font-size="16" fill="#1f77b4">S (reachable from s in residual)</text>
<text x="660" y="30" text-anchor="middle" font-family="sans-serif" font-size="16" fill="#d62728">T (not reachable)</text>
<!-- Nodes in S -->
<circle cx="120" cy="110" r="22" fill="#fff" stroke="#222" stroke-width="2"/>
<text x="120" y="116" text-anchor="middle" font-family="sans-serif" font-size="14">s</text>
<circle cx="240" cy="110" r="22" fill="#fff" stroke="#222" stroke-width="2"/>
<text x="240" y="116" text-anchor="middle" font-family="sans-serif" font-size="14">a</text>
<circle cx="180" cy="210" r="22" fill="#fff" stroke="#222" stroke-width="2"/>
<text x="180" y="216" text-anchor="middle" font-family="sans-serif" font-size="14">b</text>
<!-- Nodes in T -->
<circle cx="600" cy="110" r="22" fill="#fff" stroke="#222" stroke-width="2"/>
<text x="600" y="116" text-anchor="middle" font-family="sans-serif" font-size="14">c</text>
<circle cx="720" cy="210" r="22" fill="#fff" stroke="#222" stroke-width="2"/>
<text x="720" y="216" text-anchor="middle" font-family="sans-serif" font-size="14">t</text>
<!-- Residual edges inside S (reachable) -->
<line x1="142" y1="110" x2="218" y2="110" stroke="#222" stroke-width="2.5" marker-end="url(#arr)"/>
<text x="180" y="96" text-anchor="middle" font-family="sans-serif" font-size="12">residual>0</text>
<line x1="130" y1="126" x2="170" y2="190" stroke="#222" stroke-width="2.5" marker-end="url(#arr)"/>
<line x1="220" y1="126" x2="195" y2="188" stroke="#222" stroke-width="2.5" marker-end="url(#arr)"/>
<!-- Original edges from S to T are saturated => show as dashed and labeled saturated -->
<line x1="262" y1="110" x2="578" y2="110" stroke="#222" stroke-width="3" stroke-dasharray="6,6" marker-end="url(#arr)"/>
<text x="420" y="100" text-anchor="middle" font-family="sans-serif" font-size="12">S→T edge saturated (no residual)</text>
<line x1="202" y1="210" x2="705" y2="210" stroke="#222" stroke-width="3" stroke-dasharray="6,6" marker-end="url(#arr)"/>
<text x="455" y="200" text-anchor="middle" font-family="sans-serif" font-size="12">S→T edge saturated (no residual)</text>
<!-- Edges inside T (may exist, but unreachable from s) -->
<line x1="620" y1="126" x2="705" y2="194" stroke="#222" stroke-width="2" marker-end="url(#arr)"/>
<text x="690" y="150" text-anchor="middle" font-family="sans-serif" font-size="12">(unreached)</text>
<!-- Label: cut boundary -->
<text x="440" y="285" text-anchor="middle" font-family="sans-serif" font-size="14">Cut boundary between S and T</text>
</svg>From the final residual graph you can extract:
This is extremely useful in applications (segmentation, clustering, reliability, identifying bottlenecks) because the min cut provides interpretability: “here are the edges that truly limit throughput.”
Consider a flow network with vertices {s, a, b, t} and capacities:
We will run Ford–Fulkerson and explicitly observe a backward residual edge enabling rerouting. Start with f=0 everywhere.
Step 1: Start with zero flow.
All residual capacities equal original capacities.
|f|=0.
Step 2: Choose augmenting path P₁: s → a → b → t.
Residual capacities along edges:
Bottleneck Δ₁ = min(3,2,3)=2.
Augment by 2:
Now |f|=2.
Step 3: Build residual edges after augmentation.
Forward residuals:
Also backward residuals appear where flow>0:
Step 4: If we try path s → a → t, it exists in residual.
Residual capacities:
So Δ₂ = 1.
Augment by 1:
Now |f|=3.
(Flow conservation at a: in=3, out=2 to b +1 to t =3.)
Step 5: We still have capacity from s to b: c_f(s,b)=2.
Try path P₃: s → b → t.
Residuals:
So Δ₃=1.
Augment:
Now |f|=4.
(b→t is now saturated.)
Step 6: Can we push more? We’d like to use s→b (still has residual 1) but b→t is full.
Look for an augmenting path that uses a backward edge.
Consider residual path P₄: s → b → a → t.
Check each residual edge:
So P₄ is valid and Δ₄=min(1,2,1)=1.
Step 7: Augment along P₄ by 1.
Edge updates:
Now |f|=5.
Step 8: Verify feasibility and interpret rerouting.
At vertex b:
More systematically via original edges:
So conservation holds.
At vertex a:
So conservation holds.
Step 9: Argue optimality quickly via cut.
Consider cut S={s,a,b}, T={t}.
Cut capacity is c(a,t)+c(b,t)=2+3=5.
We have found a flow of value 5, so it must be maximum.
Insight: Backward residual edges are not a technicality; they encode “undoing” earlier routing choices. In this example, the augmenting path s→b→a→t used the backward edge b→a to reduce flow on a→b, freeing capacity so more could reach t through a→t even though b→t was already saturated.
Use the same network and the final maximum flow from Example 1 (value 5). We will construct the residual graph and compute the reachable set S from s, then read off the minimum cut (S,T).
Step 1: Record final flows from Example 1:
Flow value |f|=5.
Step 2: Compute residual capacities for each original edge.
For each edge (u,v): c_f(u,v)=c(u,v)-f(u,v).
Step 3: Add backward residual edges where f(u,v)>0 with capacity f(u,v).
Step 4: Determine which vertices are reachable from s in the residual graph.
From s, there are no outgoing residual edges (both s→a and s→b have residual 0).
So the reachable set is S={s}.
Thus T={a,b,t}.
Step 5: Compute cut capacity c(S,T).
Edges from S to T are edges leaving s:
So c(S,T)=3+2=5.
Step 6: Conclude minimum cut.
We already have |f|=5, and we found a cut with capacity 5.
So (S,T)=({s},{a,b,t}) is a minimum cut and f is a maximum flow.
Insight: At optimality, the residual graph can “strand” the source: if s has no outgoing residual edges, then S={s} immediately yields a min cut consisting of the saturated edges leaving s. More generally, S is the set reachable from s; edges crossing S→T are exactly the saturated bottleneck edges defining the min cut.
You already know linear programming and simplex. Here we sketch the primal/dual relationship that mirrors max-flow/min-cut. This is not a full dual derivation, but enough to connect the theorem to LP duality intuition.
Step 1: Write max flow (primal) as an LP.
Variables f(u,v) for each edge.
Maximize |f|, which can be written as maximize ∑_v f(s,v) - ∑_v f(v,s).
Subject to:
Step 2: Interpret conservation constraints as “no net creation” at intermediate vertices.
Only s can create net +|f| and t can absorb it.
Step 3: Dual intuition: assign potentials/labels that separate s from t.
A cut can be represented by an indicator x_v ∈ {0,1} with x_s=0, x_t=1, where S={v: x_v=0}.
Then edges crossing S→T are those with x_u=0, x_v=1.
Step 4: The min-cut objective ∑_{(u,v)} c(u,v)·[x_u=0, x_v=1] mirrors a dual objective.
Relaxing x_v to [0,1] and using constraints like y_{uv} ≥ x_v - x_u creates a linear program whose optimum equals max flow (strong duality).
Step 5: Connect to theorem.
The fact that max flow equals min cut is an instance of strong duality specialized to a network matrix, but Ford–Fulkerson gives a purely graph-theoretic constructive proof and produces the dual certificate (the cut) from residual reachability.
Insight: Max-flow/min-cut is not a coincidence: a cut is a dual witness that upper-bounds every feasible flow. The residual-graph stopping condition is the combinatorial analogue of complementary slackness: crossing edges are saturated (tight), and no residual path remains to improve the primal.
A feasible – flow satisfies capacity bounds and flow conservation at all vertices except and .
The residual network contains edges with positive residual capacity, including backward edges with capacity equal to the current flow (allowing cancellation).
An augmenting path is an path in ; augmenting by the bottleneck increases the flow value by exactly while preserving feasibility.
Ford–Fulkerson repeats augmentation until no augmenting path exists; with integer capacities it terminates (and yields an integer max flow).
Edmonds–Karp (BFS shortest augmenting paths) guarantees polynomial time: .
For any cut , every feasible flow satisfies (cuts upper-bound flows).
When no augmenting path exists, letting S be vertices reachable from s in the residual graph produces a cut with capacity equal to the current flow value.
Therefore, maximum flow equals minimum cut capacity: .
Forgetting backward residual edges (or giving them the wrong capacity): the correct backward residual capacity is exactly the current flow on the forward edge.
Updating flows incorrectly on backward edges during augmentation (you must subtract from the corresponding original forward flow).
Computing cut capacity using edges in both directions; cut capacity is only the sum of capacities of edges directed from S to T.
Assuming generic Ford–Fulkerson always terminates quickly; without a path selection rule it can be pseudo-polynomial (or non-terminating with irrational capacities).
Given a network with edges and capacities: c(s,a)=4, c(s,b)=2, c(a,b)=1, c(a,t)=2, c(b,t)=3. Start with zero flow. Perform two Ford–Fulkerson augmentations of your choice and write the resulting flow values on each edge.
Hint: Pick an s→t path, compute the bottleneck residual capacity, augment, then rebuild residual capacities before the second augmentation.
One possible sequence:
1) Path s→a→t has bottleneck min(4,2)=2. Augment: f(s,a)=2, f(a,t)=2. |f|=2.
2) Residuals now: c_f(s,a)=2, c_f(a,t)=0. Pick path s→b→t with bottleneck min(2,3)=2. Augment: f(s,b)=2, f(b,t)=2. |f|=4.
Resulting nonzero flows: f(s,a)=2, f(a,t)=2, f(s,b)=2, f(b,t)=2. (Other edges 0.)
Let f be a feasible flow and (S,T) be any s–t cut. Prove (by algebra using conservation) that .
Hint: Sum flow conservation equations over all vertices in S\{s}. The internal edges cancel; only boundary terms and source terms remain.
Consider net outflow from S:
N(S)=∑_{u∈S, v∈V} f(u,v) − ∑_{u∈V, v∈S} f(u,v).
Split sums into internal (S→S), outgoing (S→T), incoming (T→S).
Internal terms cancel because they appear once with + and once with −.
So N(S)=∑_{u∈S, v∈T} f(u,v) − ∑_{u∈T, v∈S} f(u,v).
Now use conservation: for every vertex in S except s, net outflow is 0. Therefore N(S) equals net outflow of s, which is exactly |f|. Hence the identity holds.
At termination (no augmenting path), define S as the set of vertices reachable from s in the residual graph. Prove that every original edge (u,v) with u∈S and v∈T is saturated: f(u,v)=c(u,v).
Hint: Argue by contradiction: if f(u,v)<c(u,v), then the forward residual capacity is positive and (u,v) would be a residual edge.
Suppose there exists an edge (u,v) with u∈S, v∈T, and f(u,v)<c(u,v). Then c_f(u,v)=c(u,v)−f(u,v)>0, so (u,v) is an edge in the residual graph. Since u is reachable from s and (u,v) is a residual edge, v would be reachable from s as well, contradicting v∈T=V\S. Therefore no such edge exists, and every edge crossing S→T must satisfy f(u,v)=c(u,v).
Prerequisites you can lean on:
Common next steps (typical unlocks after this node):