Flow network model
A flow network is a directed graph with capacities, a source and a sink. A flow respects the capacities and conserves material at every other vertex; the residual graph shows how a flow can still be improved.
Learning goals
- State the capacity and conservation rules and check them for a given flow
- Build the residual graph with forward and reverse arcs
- Recognise an augmenting path and explain what pushing along it does
- Explain why reverse arcs let a later path undo an earlier choice
Prerequisites
Think of pipes carrying water from a reservoir to a town. Each pipe has a maximum rate, nothing is created or lost at the junctions, and the question is how much water can reach the town. Traffic, data packets and supply chains obey the same two rules.
Source/sink
A **flow network** is a directed graph with a designated **source** s (where material enters) and **sink** t (where it leaves). In this chapter the source is vertex 0 and the sink is vertex n - 1. Every edge also has a capacity, covered next.
A **flow** puts a non-negative amount of material on each edge. Its **value** is the net amount leaving the source: flow on edges out of s minus flow on edges into s. By conservation (below) it equals the net amount arriving at the sink, so you can measure it at either end.
The goal of the whole chapter is the **maximum flow**: the largest value any valid flow can have. Open the animation and load “A valid flow of value 4”: the log checks every rule and then reports the value, 4.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static int flowValue(int[][] edges) { // net flow leaving the source3int value = 0;4for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }5return value;6}78static int flowIntoSink(int n, int[][] edges) { // net flow arriving at the sink: equals flowValue for a valid flow9int value = 0;10for (int[] e : edges) { if (e[1] == n - 1) value += e[3]; if (e[0] == n - 1) value -= e[3]; }11return value;12}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Capacity
Each edge u → v has a **capacity** c, the most it can carry. A flow f must satisfy 0 <= f(u, v) <= c(u, v) for every edge. Capacities are whole numbers in this course's examples, but real networks (bandwidth, pipe diameters) use any non-negative number.
Load “Capacity exceeded on 1-3”: edge 1-3 has capacity 2 but is given 3 units. The animation flags that edge and reports the flow as invalid even though every vertex is balanced, because a flow has to satisfy both rules.
Parallel edges are allowed and are simply separate pipes between the same two vertices; their capacities add up.
1static boolean respectsCapacity(int[][] edges) {2for (int[] e : edges) if (e[3] < 0 || e[3] > e[2]) return false; // 0 <= flow <= capacity on every edge3return true;4}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Conservation
**Conservation** says that every vertex other than the source and the sink passes on exactly what it receives: inflow equals outflow. Nothing is stored, created or destroyed in the middle of the network.
Load “Conservation broken at vertex 1”: vertex 1 receives 3 units on 0-1 but sends only 1 unit on 1-3. Two units vanish, so this is not a flow. The check is easy to implement as a balance array: subtract the flow from the tail and add it to the head of every edge, then every inner vertex must end at 0.
Conservation is also why the flow value can be measured at either end: the amount that leaves the source must arrive at the sink, because it cannot accumulate anywhere else.
1static boolean conservationHolds(int n, int[][] edges) {2int[] balance = new int[n]; // inflow minus outflow3for (int[] e : edges) { balance[e[0]] -= e[3]; balance[e[1]] += e[3]; }4for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false;5return true;6}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Residual graph
The **residual graph** describes what can still change. For every edge u → v with capacity c and flow f it has up to two arcs: a **forward arc** u → v with residual capacity c - f (room left), and a **backward arc** v → u with residual capacity f (flow that could be cancelled). Arcs with residual capacity 0 are left out.
Load the “A valid flow of value 4” preset and run the first question. The graph has 5 edges and 7 residual arcs: 0-1 (2 of 3) gives a forward arc of 1 and a backward arc of 2; 0-2 (2 of 2) is full so only its backward arc remains; 1-2 (0 of 1) has only a forward arc; 1-3 (2 of 2) only a backward arc; 2-3 (2 of 3) both. In the animation the arcs marked with * are the backward ones.
The residual graph is the working data structure of every algorithm in this chapter. A path in it from the source to the sink is an augmenting path.
1static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}2List<int[]> arcs = new ArrayList<>();3for (int[] e : edges) {4if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left5if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled6}7return arcs;8}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Augmenting paths
An **augmenting path** is a path from the source to the sink in the residual graph. Its **bottleneck** is the smallest residual capacity on it. Pushing the bottleneck along the path raises the flow value by that amount: forward arcs gain flow, backward arcs lose flow.
If the residual graph has no such path the flow cannot be improved by this method, and (as the next lesson proves) it is already maximum. Load “Not maximum: an augmenting path remains” with the third question: the residual search reaches the sink along 0→1→2→3, so the flow of value 4 can still grow. Load “Maximum: the residual graph cuts off the sink” and the search stops at the source.
The code finds some augmenting path with BFS over the residual arcs. Algorithms differ only in how they choose the path.
1static List<Integer> augmentingPath(int n, int[][] edges) { // vertices of an s-t path in the residual graph, or empty2List<int[]> arcs = residualArcs(edges);3int[] parent = new int[n];4Arrays.fill(parent, -1);5parent[0] = 0;6Deque<Integer> queue = new ArrayDeque<>(List.of(0));7while (!queue.isEmpty() && parent[n - 1] == -1) {8int u = queue.poll();9for (int[] a : arcs) if (a[0] == u && parent[a[1]] == -1) { parent[a[1]] = u; queue.add(a[1]); }10}11LinkedList<Integer> path = new LinkedList<>();12if (parent[n - 1] == -1) return path;13for (int v = n - 1; v != 0; v = parent[v]) path.addFirst(v);14path.addFirst(0);15return path;16}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Reverse edges
Why do backward arcs exist? Because a greedy choice may turn out wrong. On the “Reverse edges are needed” preset (0-1:1 0-2:1 1-2:1 1-3:1 2-3:1) the first path found is 0→1→2→3. That saturates 0-1, 1-2 and 2-3. Edge 0-2 is still free but leads to vertex 2, whose only way out (2-3) is full; edge 1-3 is free but can only be reached through vertex 1, whose entry (0-1) is full. A search that cannot undo anything is stuck at flow 1, yet the maximum flow is 2. The second path is 0→2→1→3, and its middle step 2→1 is a **backward arc**: it cancels the unit that 1-2 carried.
Cancelling is not an accident. A flow of value 2 here sends one unit along 0→1→3 and one along 0→2→3; the backward arc lets the algorithm rearrange the first path into that without ever starting over. Without backward arcs, augmenting-path algorithms can get stuck at a flow that is not maximum.
In code, store every edge as a **pair of arcs** with ids k and k ^ 1: the forward arc with the capacity and the backward arc with capacity 0. Pushing d units along arc k does cap[k] -= d and cap[k ^ 1] += d, which handles forward and backward moves in one line. The flow on the original edge is simply cap[k ^ 1].
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap; // arc k and arc k ^ 1 are each other's reverse5int arcs = 0;67@SuppressWarnings("unchecked")8Net(int n, int maxEdges) {9this.n = n; adj = new List[n]; to = new int[2 * maxEdges]; cap = new int[2 * maxEdges];10for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();11}1213int addEdge(int u, int v, int c) { // returns the id of the forward arc; its flow is cap[id ^ 1]14int id = arcs;15to[arcs] = v; cap[arcs] = c; adj[u].add(arcs++); // forward arc with the capacity16to[arcs] = u; cap[arcs] = 0; adj[v].add(arcs++); // reverse arc: capacity 0 until flow is pushed17return id;18}1920void push(int id, int d) { cap[id] -= d; cap[id ^ 1] += d; } // use d units: shrink the arc, grow its reverse21}
Maximum flow: Ford-Fulkerson, Edmonds-Karp, Dinic, push-relabel
Interactive · edit the inputedge label: flow / capacity · 6 vertices · 9 edges
source
sink
residual arcs u→v : remaining capacity
Every edge starts with flow 0, so every arc has its full capacity as residual capacity and every reverse arc has 0. Edmonds-Karp (BFS paths) looks for ways to send more from 0 to 5. The label on an edge is flow / capacity.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap; // arc k and arc k ^ 1 are each other's reverse5int arcs = 0;6@SuppressWarnings("unchecked")7Net(int n, int maxEdges) {8this.n = n; adj = new List[n]; to = new int[2 * maxEdges]; cap = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c) {12to[arcs] = v; cap[arcs] = c; adj[u].add(arcs++); // forward arc with the capacity13to[arcs] = u; cap[arcs] = 0; adj[v].add(arcs++); // reverse arc: capacity 0 until flow is pushed14}15}16static int fordFulkerson(Net g, int s, int t) {17int flow = 0;18while (true) {19int pushed = dfs(g, s, t, Integer.MAX_VALUE, new boolean[g.n]); // any augmenting path, found depth-first20if (pushed == 0) return flow;21flow += pushed;22}23}24static int dfs(Net g, int u, int t, int f, boolean[] seen) {25if (u == t) return f;26seen[u] = true;27for (int id : g.adj[u]) {28int v = g.to[id];29if (seen[v] || g.cap[id] == 0) continue;30int d = dfs(g, v, t, Math.min(f, g.cap[id]), seen);31if (d > 0) { g.cap[id] -= d; g.cap[id ^ 1] += d; return d; } // use d units, open d units of reverse capacity32}33return 0;34}35static int edmondsKarp(Net g, int s, int t) {36int flow = 0;37while (true) {38int[] viaArc = new int[g.n];39Arrays.fill(viaArc, -1);40Deque<Integer> queue = new ArrayDeque<>(List.of(s));41while (!queue.isEmpty() && viaArc[t] == -1) { // BFS: the path with fewest arcs42int u = queue.poll();43for (int id : g.adj[u]) {44int v = g.to[id];45if (v != s && viaArc[v] == -1 && g.cap[id] > 0) { viaArc[v] = id; queue.add(v); }46}47}48if (viaArc[t] == -1) return flow;49int bottleneck = Integer.MAX_VALUE;50for (int v = t; v != s; v = g.to[viaArc[v] ^ 1]) bottleneck = Math.min(bottleneck, g.cap[viaArc[v]]);51for (int v = t; v != s; v = g.to[viaArc[v] ^ 1]) { g.cap[viaArc[v]] -= bottleneck; g.cap[viaArc[v] ^ 1] += bottleneck; }52flow += bottleneck;53}54}55static int dinic(Net g, int s, int t) {56int flow = 0;57int[] level = new int[g.n];58while (levels(g, s, t, level)) { // new level graph; stop when t is unreachable59int[] next = new int[g.n]; // next arc to try at each vertex60for (int f; (f = blocking(g, s, t, Integer.MAX_VALUE, level, next)) > 0; ) flow += f;61}62return flow;63}64static boolean levels(Net g, int s, int t, int[] level) {65Arrays.fill(level, -1);66level[s] = 0;67Deque<Integer> queue = new ArrayDeque<>(List.of(s));68while (!queue.isEmpty()) {69int u = queue.poll();70for (int id : g.adj[u]) if (g.cap[id] > 0 && level[g.to[id]] == -1) { level[g.to[id]] = level[u] + 1; queue.add(g.to[id]); }71}72return level[t] != -1;73}74static int blocking(Net g, int u, int t, int f, int[] level, int[] next) {75if (u == t) return f;76for (; next[u] < g.adj[u].size(); next[u]++) {77int id = g.adj[u].get(next[u]), v = g.to[id];78if (g.cap[id] == 0 || level[v] != level[u] + 1) continue; // only arcs one level deeper79int d = blocking(g, v, t, Math.min(f, g.cap[id]), level, next);80if (d > 0) { g.cap[id] -= d; g.cap[id ^ 1] += d; return d; }81}82return 0;83}84static int pushRelabel(Net g, int s, int t) {85int[] height = new int[g.n], excess = new int[g.n];86height[s] = g.n; // the source starts as high as possible87Deque<Integer> active = new ArrayDeque<>();88for (int id : g.adj[s]) { // saturate every arc out of the source89int c = g.cap[id], v = g.to[id];90if (c == 0) continue;91g.cap[id] = 0; g.cap[id ^ 1] += c; excess[v] += c;92if (v != t && excess[v] == c) active.add(v);93}94while (!active.isEmpty()) discharge(g, active.poll(), s, t, height, excess, active);95return excess[t];96}97static void discharge(Net g, int u, int s, int t, int[] height, int[] excess, Deque<Integer> active) {98while (excess[u] > 0) {99boolean pushed = false;100for (int id : g.adj[u]) {101int v = g.to[id];102if (g.cap[id] > 0 && height[u] == height[v] + 1) { // push only downhill, by exactly one level103int d = Math.min(excess[u], g.cap[id]);104g.cap[id] -= d; g.cap[id ^ 1] += d; excess[u] -= d; excess[v] += d;105if (v != s && v != t && excess[v] == d) active.add(v);106pushed = true;107if (excess[u] == 0) break;108}109}110if (!pushed) { // stuck: relabel one above its lowest residual neighbour111int lowest = Integer.MAX_VALUE;112for (int id : g.adj[u]) if (g.cap[id] > 0) lowest = Math.min(lowest, height[g.to[id]]);113height[u] = lowest + 1;114}115}116}
Live state
n
6
edges
9
source
0
sink
5
Execution trace (dry run table)Show 11 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=6 edges=9 source=0 sink=5 | Every edge starts with flow 0, so every arc has its full capacity as residual capacity and every reverse arc has 0. Edmonds-Karp (BFS paths) looks for ways to send more from 0 to 5. The label on an edge is flow / capacity. |
| 2 | L48 | path=0→1→3→5 bottleneck=4 flow=0 | Breadth-first search (fewest arcs) finds the path 0→1→3→5 with 3 arcs. Its smallest residual capacity is 4. |
| 3 | L51 | bottleneck=4 flow=4 | Push 4 along the path. Flow value is now 4. |
| 4 | L48 | path=0→1→4→5 bottleneck=6 flow=4 | Breadth-first search (fewest arcs) finds the path 0→1→4→5 with 3 arcs. Its smallest residual capacity is 6. |
| 5 | L51 | bottleneck=6 flow=10 | Push 6 along the path. Flow value is now 10. |
| 6 | L48 | path=0→2→4→5 bottleneck=4 flow=10 | Breadth-first search (fewest arcs) finds the path 0→2→4→5 with 3 arcs. Its smallest residual capacity is 4. |
| 7 | L51 | bottleneck=4 flow=14 | Push 4 along the path. Flow value is now 14. |
| 8 | L48 | path=0→2→4→3→5 bottleneck=5 flow=14 | Breadth-first search (fewest arcs) finds the path 0→2→4→3→5 with 4 arcs. Its smallest residual capacity is 5. |
| 9 | L51 | bottleneck=5 flow=19 | Push 5 along the path. Flow value is now 19. |
| 10 | L48 | flow=19 | No path with positive residual capacity from 0 to 5 remains: the search ends. |
| 11 | L16 | return=19 cut_capacity=19 augmentations=4 | Maximum flow = 19. The vertices still reachable from 0 in the residual graph, {0, 2}, form the minimum cut: the edges leaving that set (0→1, 2→4) are all saturated and their capacities add up to 19, the same number. |
Practice ladder
Now try it yourself
Easy
- Find the Town Judge
Medium
- Number of Provinces
- Cheapest Flights Within K Stops
Advanced
- Remove Max Number of Edges to Keep Graph Fully Traversable
- Minimum Cost to Make at Least One Valid Path in a Grid
Key takeaways
- A flow obeys two rules: 0 <= flow <= capacity on each edge, and inflow = outflow at every inner vertex.
- The residual graph has a forward arc (capacity - flow) and a backward arc (flow) per edge.
- An augmenting path is a source-sink path in the residual graph; backward arcs let later paths undo earlier choices.