Dinic's algorithm
Dinic's algorithm works in phases. Each phase builds a BFS level graph and then pushes a blocking flow through it with a DFS that remembers dead ends; the sink distance grows every phase, so there are at most V phases.
Learning goals
- Build a level graph from the residual graph
- Compute a blocking flow with a current-arc pointer
- Explain why there are at most V phases
- Know the bounds for unit-capacity networks
Prerequisites
Edmonds-Karp runs one BFS per path. Dinic runs one BFS and then reuses its map of distances to send many paths before searching again, like surveying the roads once and then driving many trucks along downhill routes.
Level graph
Run a BFS from the source over arcs with positive residual capacity and record the distance level[v] of each vertex. The **level graph** keeps only arcs that go from level i to level i + 1. Every source-sink path inside it is a shortest residual path.
Load the “Dinic on the same network” preset. Phase 1 reports that the sink is at level 3, and the animation's *level of each vertex* row shows the levels. The BFS also tells you when to stop: if the sink is unreachable (level[t] == -1) the flow is maximum.
Arcs that stay in the same level or go backwards are ignored in this phase even though they have capacity.
1static boolean levels(Net g, int s, int t, int[] level) {2Arrays.fill(level, -1);3level[s] = 0;4Deque<Integer> queue = new ArrayDeque<>(List.of(s));5while (!queue.isEmpty()) {6int u = queue.poll();7for (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]); }8}9return level[t] != -1; // false: no augmenting path, the flow is maximum10}
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. |
Blocking flow
A **blocking flow** in the level graph is a flow that saturates at least one arc on every source-sink path of the level graph, so no further path exists there. It is not necessarily a maximum flow of the whole network; it only blocks this phase's shortest paths.
Compute it with DFS that only follows arcs one level deeper. The DFS keeps a pointer next[u] to the first arc at u that might still work. When an arc is saturated or leads to a dead end, the pointer moves past it and never comes back in this phase. That is what makes a whole phase cost O(V · E): each arc is skipped at most once per vertex and each successful path costs O(V).
In the sample phase 1 the animation finds three paths: 0→1→3→5 with 4 units, 0→1→4→5 with 6 units and 0→2→4→5 with 4 units, pushing the flow to 14 before the phase ends.
1static int blocking(Net g, int u, int t, int f, int[] level, int[] next) {2if (u == t) return f;3for (; next[u] < g.adj[u].size(); next[u]++) { // next[u]: first arc at u that may still work4int id = g.adj[u].get(next[u]), v = g.to[id];5if (g.cap[id] == 0 || level[v] != level[u] + 1) continue; // only arcs one level deeper6int d = blocking(g, v, t, Math.min(f, g.cap[id]), level, next);7if (d > 0) { g.cap[id] -= d; g.cap[id ^ 1] += d; return d; }8}9return 0; // dead end: every arc at u was tried10}
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. |
DFS augmentation
The full algorithm alternates the two steps. Phase: BFS to get levels; if the sink is unreachable stop; otherwise reset next and call the blocking DFS repeatedly from the source until it returns 0, adding each pushed amount to the flow.
After a phase the residual graph has no shortest path of the old length left, so the next BFS gives a strictly larger sink distance. The sample network needs two phases: levels 3 and then 4. The second phase finds one more path, 0→2→4→3→5, which has 4 arcs, longer than any path the first phase could use, and pushes 5 units to reach 19.
Each phase costs O(V · E) and the sink distance, which is at most V − 1, grows every phase, so the total is O(V² · E).
1static int dinic(Net g, int s, int t) {2int flow = 0;3int[] level = new int[g.n];4while (levels(g, s, t, level)) { // a new level graph; stop when t is unreachable5int[] next = new int[g.n]; // fresh arc pointers for the phase6for (int f; (f = blocking(g, s, t, Integer.MAX_VALUE, level, next)) > 0; ) flow += f;7}8return flow;9}
Worst
O(V² · E)
Space
O(V + E)
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. |
Complexity
General networks: at most V − 1 phases (the sink distance grows each phase) and O(V · E) per phase gives O(V² · E). In practice Dinic is far faster than the bound, because phases are few and each blocking flow finds many paths at once.
For comparison on a graph with V vertices and E edges:
| Algorithm | Time | Depends on capacities? |
|---|---|---|
| Ford-Fulkerson (DFS) | O(E · |f|) | yes |
| Edmonds-Karp | O(V · E²) | no |
| Dinic | O(V² · E) | no |
| Dinic, unit capacities | O(E · min(V^(2/3), E^(1/2))) | no |
| Push-relabel (FIFO) | O(V³) | no |
Unit capacity variations
When every edge has capacity 1 (or, more generally, every vertex has a single incoming or outgoing unit edge), Dinic is much faster: each phase is O(E) and there are only O(√E) phases for unit networks, O(√V) for bipartite matching networks. This is the source of the Hopcroft-Karp bound in Chapter 40.
The code builds the standard matching network: a source joined to every left vertex, every allowed pair as a unit edge, and every right vertex joined to a sink, then runs Dinic. The maximum flow is the size of a maximum matching.
Load the “Dinic on the same network” preset and then set every capacity to 1 in the editor: the phases become short, and the number of augmentations equals the final flow.
1static int maxMatching(int left, int right, int[][] pairs) { // pairs {leftIndex, rightIndex}2int s = 0, t = left + right + 1;3Net g = new Net(t + 1, left + right + pairs.length);4for (int i = 0; i < left; i++) g.addEdge(s, 1 + i, 1);5for (int j = 0; j < right; j++) g.addEdge(1 + left + j, t, 1);6for (int[] p : pairs) g.addEdge(1 + p[0], 1 + left + p[1], 1);7return dinic(g, s, t);8}
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 Center of Star Graph
Medium
- Number of Provinces
- Is Graph Bipartite?
Advanced
- Maximum Students Taking Exam
- Distribute Repeating Integers
Key takeaways
- Dinic: BFS levels, then a blocking flow with a DFS that keeps a current-arc pointer; repeat until the sink is unreachable.
- The sink distance rises every phase, so there are at most V phases; O(V²·E) overall.
- On unit-capacity networks Dinic is much faster, which underlies Hopcroft-Karp for matching.