A Bit of History
As covered in Part 15, L. R. Ford Jr. and D. R. Fulkerson published this method in their 1956 RAND Corporation report "Maximal Flow Through a Network," directly motivated by a classified 1955 analysis of Soviet railway capacity. Their method wasn't just an algorithm — it came paired with the max-flow min-cut theorem's proof, making it one of the rare cases in this series where an algorithm and its correctness proof were published together as two faces of the same insight.
Working Principle
The Ford-Fulkerson method (deliberately not called a fully-specified "algorithm" — it leaves one choice open, discussed below) repeatedly finds any augmenting path from \(s\) to \(t\) in the current residual graph (Part 15's construction), pushes flow equal to that path's bottleneck capacity, and updates the residual graph accordingly. It terminates exactly when no augmenting path remains — at which point, by the max-flow min-cut argument from Part 15, the current flow is provably maximum.
def ford_fulkerson_pseudocode(capacity, source, sink, vertices):
"""capacity: dict[(u,v)] -> capacity. Builds residual capacities as it goes."""
residual = dict(capacity)
for u, v in list(capacity):
residual.setdefault((v, u), 0) # reverse residual edges start at 0
def find_augmenting_path():
# any path-finding method works here -- DFS, BFS, etc.
parent = {source: None}
stack = [source]
while stack:
u = stack.pop()
if u == sink:
break
for v in vertices:
if residual.get((u, v), 0) > 0 and v not in parent:
parent[v] = u
stack.append(v)
return parent if sink in parent else None
max_flow = 0
while (parent := find_augmenting_path()):
# find bottleneck capacity along the path
path, v = [], sink
while v is not None:
path.append(v)
v = parent[v]
path.reverse()
bottleneck = min(residual[(path[i], path[i+1])] for i in range(len(path)-1))
for i in range(len(path) - 1):
u, v = path[i], path[i+1]
residual[(u, v)] -= bottleneck
residual[(v, u)] += bottleneck # enable "undoing" this flow later
max_flow += bottleneck
return max_flow
Worked Example
A small network: \(S \to A\) (cap 3), \(S \to B\) (cap 2), \(A \to T\) (cap 2), \(B \to T\) (cap 3), \(A \to B\) (cap 1).
flowchart LR
S -->|3| A
S -->|2| B
A -->|2| T
B -->|3| T
A -->|1| B
First augmenting path \(S \to A \to T\), bottleneck \(\min(3,2)=2\): push 2 units, saturating \(A \to T\). Second augmenting path \(S \to B \to T\), bottleneck \(\min(2,3)=2\): push 2 more units. Third, using the leftover \(A \to B\) capacity: \(S \to A \to B \to T\), bottleneck \(\min(1, 1, 1) = 1\) (only 1 unit of \(S \to A\) capacity remains, and \(B \to T\) has 1 unit of capacity left after the second path). Total max flow: \(2 + 2 + 1 = 5\).
Complexity Analysis
Each augmenting path increases the flow by at least 1 (assuming integer capacities), and the maximum possible flow is bounded by the sum of capacities out of the source, \(F\):
$$\text{Time: } O(E \cdot F) \qquad \text{Space: } O(V + E)$$
This bound depends on the numeric value \(F\), not just the graph's size — a genuinely unusual complexity class called pseudo-polynomial, since \(F\) can be exponentially large relative to the number of bits needed to write the capacities down.
A Pathological Case
Bad Path Choices Can Make It Crawl
Consider a network with capacities in the millions, where a poorly chosen sequence of augmenting paths repeatedly sends flow back and forth along a small cycle-like structure, incrementing the total flow by just 1 unit per augmentation instead of jumping straight to the maximum. With unlucky (or adversarial) path selection, Ford-Fulkerson can take millions of iterations to converge on a graph with only a handful of vertices — the exact motivation for the Edmonds-Karp refinement (next deep dive), which fixes this by specifying which augmenting path to use.
Implementation
Real-World Applications
Supply Chain Capacity Planning
Beyond its Cold War railway origins, Ford-Fulkerson-style flow models are used today to plan supply-chain and logistics networks — warehouses as vertices, shipping-lane throughput as capacities — to determine the maximum sustainable shipment rate from factories to retail distribution centers, and to identify which specific lanes are the true bottleneck (via the min-cut side of the same computation).
Exercises
- Run Ford-Fulkerson by hand on the worked example using a different order of augmenting paths, and confirm you still reach the same maximum flow value of 5.
- Explain, using the residual-graph argument from Part 15, why the algorithm's termination (no augmenting path left) guarantees optimality rather than just "no more obvious improvement."
- Construct a small network with irrational or very large capacities and describe (without fully simulating) why an unlucky path choice could make convergence extremely slow.
- Challenge: Modify the implementation to use BFS instead of DFS for finding augmenting paths, and compare the number of iterations needed on a larger random network — this is exactly the Edmonds-Karp refinement, previewed next.
Limitations
Path Choice Is Not Specified — And It Matters
Because Ford-Fulkerson doesn't specify how to find an augmenting path, its worst-case running time depends on capacity values, not just graph size — and can be made arbitrarily slow by adversarial capacities with an unlucky path-finding strategy. This single unspecified choice is exactly what the Edmonds-Karp refinement fixes, guaranteeing polynomial time regardless of capacity values.