DSA Graphs
Maximum Flow
The CDN must push as many Gbps as possible from origin s to edge PoP t. Pipes have capacities. Question-led: when is a routing plan optimal? When its equals a .
Algorithms search the for an (or push preflow). Residual reverse edges are how you undo a bad earlier push without restarting.
Algorithm journey
Capacities + conservation
Valid flow contract first.
Forward leftover + reverse undo.
Augment or push
Raise value; update residuals.
No s→t residual path ⇒ optimal.
What actually happens
Naive idea: pick disjoint paths greedily and sum bottlenecks. Breaks when the optimum needs to partially cancel flow on an edge (residual reverse) to free a better combination.
Better idea: maintain a feasible flow and residual graph. Augment along residual s–t paths until none remain. Then residual reachability from s defines a min cut of equal capacity.
The improved design still fails with bad path choice on irrational capacities (non-termination), with modeling bugs (missing reverse edges), or if you confuse cut capacity with residual capacity.
Invariant
Every intermediate state is a feasible flow. Flow value never exceeds any s–t cut capacity. When no residual s–t path exists, value equals the residual source-side cut capacity (max-flow min-cut).
How to choose a max-flow method
1. Teaching / small graphs? Ford-Fulkerson intuition; Edmonds-Karp for a polynomial guarantee.
2. Sparse competitive graphs? Dinic is a strong default.
3. Dense / practical large nets? Push-relabel variants often win.
Quantitative check
Edmonds-Karp is O(VE²). At V=200, E=1000 that is ~4×10⁷ edge scans in the bound: usually fine. At V=5×10³, E=2×10⁴, prefer Dinic or push-relabel.
Interactive residual-network lab
Residual-network invariant lab
Step through augmentation, then read the minimum cut directly from residual reachability.
Stage 1 of 4
Zero flow
Every forward residual capacity equals its capacity; every paired reverse edge starts at 0.
| Edge | Forward | Reverse |
|---|---|---|
| s → a | 10 | 0 |
| s → b | 5 | 0 |
| a → b | 15 | 0 |
| a → t | 10 | 0 |
| b → t | 10 | 0 |
The contract a valid flow must satisfy
Capacity
For every directed edge, 0 ≤ f(u,v) ≤ c(u,v). Capacities must be non-negative.
Skew symmetry
f(u,v) = −f(v,u) in the paired-edge model. A reverse edge records the right to cancel earlier flow.
Conservation
Every vertex except s and t has equal inflow and outflow. The source's net outflow is the flow value.
🔑 Key Concepts
Source (s)
The distinguished starting vertex. Flow value is its net outflow; incoming edges are allowed by the formal model.
Sink (t)
The distinguished destination vertex. Its net inflow equals the flow value; outgoing edges need not be forbidden.
Capacity c(u, v)
The maximum amount of flow that can pass through an edge from u to v. Flow cannot exceed capacity.
Flow f(u, v)
The actual amount of flow passing through edge (u, v). Must satisfy: 0 ≤ f(u, v) ≤ c(u, v).
🎯 Key Insight: Flow Conservation
For every node except the source and sink, the total flow coming IN must equal the total flow going OUT. This is called flow conservation - like water flowing through a pipe junction: what comes in must go out!
∑ f(u, v) = ∑ f(v, w) for all v ≠ s, t
🔄 Residual Graph
The residual graph shows the remaining capacity in the network after some flow has been sent. It's crucial for finding augmenting paths.
Forward Edge
Residual capacity = c(u, v) - f(u, v)
How much more flow can be pushed
Backward Edge
Residual capacity = f(u, v)
How much flow can be "undone"
Implement both directions as a paired object: if edge i points to v, store the index of its reverse edge in adj[v]. An augmentation of Δ subtracts Δ from the forward residual capacity and adds it to the paired reverse. This representation keeps parallel and antiparallel input edges distinct.
A safe residual-edge primitive in both languages
Python
class Edge:
def __init__(self, to: int, rev: int, residual: int) -> None:
self.to = to
self.rev = rev
self.residual = residual
class ResidualNetwork:
def __init__(self, n: int) -> None:
if n < 0:
raise ValueError("vertex count must be non-negative")
self.adj = [[] for _ in range(n)]
def add_edge(self, u: int, v: int, capacity: int) -> None:
if not (0 <= u < len(self.adj) and 0 <= v < len(self.adj)):
raise IndexError("vertex out of range")
if u == v:
raise ValueError("self-loops are not supported")
if capacity < 0:
raise ValueError("capacity must be non-negative")
forward = Edge(v, len(self.adj[v]), capacity)
reverse = Edge(u, len(self.adj[u]), 0)
self.adj[u].append(forward)
self.adj[v].append(reverse)
def push(self, u: int, edge_index: int, amount: int) -> None:
edge = self.adj[u][edge_index]
if amount < 0 or amount > edge.residual:
raise ValueError("invalid augmentation")
edge.residual -= amount
self.adj[edge.to][edge.rev].residual += amount
network = ResidualNetwork(2)
network.add_edge(0, 1, 7)
network.push(0, 0, 3)
assert network.adj[0][0].residual == 4
assert network.adj[1][0].residual == 3Java
import java.util.ArrayList;
import java.util.List;
public class ResidualNetwork {
static final class Edge {
final int to;
final int rev;
long residual;
Edge(int to, int rev, long residual) {
this.to = to;
this.rev = rev;
this.residual = residual;
}
}
private final List<List<Edge>> adj;
ResidualNetwork(int n) {
if (n < 0) throw new IllegalArgumentException("negative vertex count");
adj = new ArrayList<>(n);
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
}
void addEdge(int u, int v, long capacity) {
if (u < 0 || u >= adj.size() || v < 0 || v >= adj.size()) {
throw new IndexOutOfBoundsException("vertex out of range");
}
if (u == v) throw new IllegalArgumentException("self-loop");
if (capacity < 0) throw new IllegalArgumentException("negative capacity");
Edge forward = new Edge(v, adj.get(v).size(), capacity);
Edge reverse = new Edge(u, adj.get(u).size(), 0L);
adj.get(u).add(forward);
adj.get(v).add(reverse);
}
void push(int u, int edgeIndex, long amount) {
Edge edge = adj.get(u).get(edgeIndex);
if (amount < 0 || amount > edge.residual) {
throw new IllegalArgumentException("invalid augmentation");
}
edge.residual -= amount;
adj.get(edge.to).get(edge.rev).residual += amount;
}
public static void main(String[] args) {
ResidualNetwork network = new ResidualNetwork(2);
network.addEdge(0, 1, 7L);
network.push(0, 0, 3L);
assert network.adj.get(0).get(0).residual == 4L;
assert network.adj.get(1).get(0).residual == 3L;
}
}🔍 Did You Know? Max-Flow Min-Cut Theorem
One of the most beautiful results in graph theory: The maximum flow from source to sink equals the minimum cut capacity! A "cut" is a partition of vertices into two sets (one containing s, the other t), and its capacity is the sum of edge capacities crossing from s's side to t's side. This theorem connects two seemingly different problems!
Why max-flow equals min-cut
Weak direction: any flow crosses every s–t cut, so conservation and capacity imply |f| ≤ capacity(S,T).
Certificate at termination: after no augmenting path remains, run DFS/BFS from s over positive residual edges. Reachable vertices form S; the rest form T. Every original edge from S to T is saturated and every edge from T to S carries zero net contribution, so the inequalities become equality.
Integrality: with integer capacities, augmenting-path algorithms push integer bottlenecks, so they return an integral maximum flow. This is why matching reductions produce discrete assignments without rounding.
🛠️ Algorithms for Max Flow
| Algorithm | Method | Time Complexity | Best For |
|---|---|---|---|
| Ford-Fulkerson | Any path finding (DFS/BFS) | O(E · |f*|), integral capacities | Teaching; small integral flow values |
| Edmonds-Karp | BFS for shortest path | O(V E²) | Small graphs; simple reference code |
| Dinic's | Level graphs + blocking flows | O(V² E) | Sparse graphs, matching, contest code |
| Push-Relabel | Preflow pushing | O(V² E) | Large or dense graphs; tuned solvers |
Modeling patterns that show up often
Bipartite matching
source → left side (1), candidate edges (1), right side → sink (1). The integral flow selects pairs.
Vertex capacities
Split v into v_in → v_out with the vertex capacity; redirect incoming and outgoing edges.
Multiple sources or sinks
Add a super-source/super-sink. Use the sum of adjacent capacities as a safe finite bound instead of a magic infinity.
Edge-disjoint paths
Give every original edge capacity 1. The maximum integral flow equals the maximum number of edge-disjoint paths.
Feasible circulation
Add demands/lower bounds, subtract lower bounds, track vertex imbalances, then connect a super-source and super-sink.
Minimum cut extraction
After max-flow, residual reachability from s labels the two sides and gives a checkable optimality certificate.
Production and edge-case checklist
- • Reject negative capacities and invalid vertex ids.
- • Define source == sink; robust APIs usually reject it.
- • Keep a separate reverse-edge index; do not merge antiparallel edges accidentally. Reject or explicitly ignore self-loops.
- • Permit parallel edges by appending, or aggregate with overflow checks.
- • Use Python integers / Java long for capacities and total flow.
- • Unreachable sink is valid and returns flow 0 plus the initial cut.
- • Never mutate the caller's graph unless documented; these residual-network objects are one-shot.
- • Verify conservation, capacity bounds, and flow == cut capacity in tests.
💼 Real World Applications
🚗 Traffic Flow Optimization
Modeling road networks to find maximum traffic flow from suburbs to city center, considering road capacities and intersections.
Example: Rush hour traffic management
✈️ Airline Scheduling
Maximizing passenger flow through airline networks, considering aircraft capacities and airport constraints.
Example: Hub-and-spoke airline networks
🖼️ Image Segmentation
Separating foreground from background in images by modeling pixels as a flow network and finding the min-cut.
Example: Photo editing "magic wand" tool
👥 Bipartite Matching
Matching jobs to workers, students to projects, or organs to patients by modeling as a max flow problem.
Example: Job assignment optimization
🌐 Network Bandwidth
Determining maximum data throughput in computer networks, considering router and link capacities.
Example: CDN optimization
⚡ Power Grid
Optimizing electricity distribution from power plants to consumers through transmission lines with capacity limits.
Example: Load balancing in power grids
💡 Pro Tip: Choosing the Right Algorithm
Use Edmonds-Karp when clarity and a simple polynomial baseline matter, Dinic's for most interview/contest sparse graphs and matching reductions, and a tuned Push-Relabel implementation for large dense instances. Benchmark on representative topology: asymptotic bounds do not predict every workload.
Short takeaway
Maximum flow equals minimum cut. Search residuals (or push preflow) until no residual s–t path remains; reverse residual edges are how you undo bad earlier routing.
Exercises
- Draw a network where a greedy disjoint-path packing is suboptimal, but one residual reverse augmentation recovers the true max flow.
- After a maximum flow, BFS in the residual graph from s. Which edges of the original network cross the min cut?
- Why can flow value never exceed any cut capacity? Give the one-line counting argument.
Primary sources
- Ford & Fulkerson: Flows in Networks; max-flow min-cut.
- Cormen et al., CLRS: Maximum flow chapter (residuals, cuts, augmenting paths).
- Ahuja, Magnanti, Orlin: Network Flows.