Flow modeling
Many problems that look unrelated to flows become max-flow questions once you build the right network: disjoint paths, vertex limits, assignments, shipments, and circulations with demands and lower bounds.
Learning goals
- Reduce edge-disjoint and vertex-disjoint paths to max flow
- Model an assignment as a flow
- Handle several sources and sinks with a super source and sink
- Decide feasibility of a circulation with demands and lower bounds
Prerequisites
Flow is a modelling language. The skill is to turn a story (“at most one worker per job”, “each room can be used once”) into capacities, and then let a standard algorithm answer.
Edge-disjoint paths
**Question**: how many paths from s to t can be found so that no edge is used by two paths? Give every edge capacity 1. An integral flow of value k decomposes into k paths that share no edge, and conversely k edge-disjoint paths are a flow of value k. By max-flow min-cut the answer also equals the fewest edges whose removal separates t from s (Menger's theorem).
Try the first preset, “Edge-disjoint paths: a diamond with a cross edge” (0-1 0-2 1-3 2-3 1-2): the answer is 2. On the 6-vertex preset “Same graph: edge-disjoint paths” (0-1 0-2 1-3 2-3 3-4 3-5 4-5) it is also 2, for example 0→1→3→4→5 and 0→2→3→5.
The helper maxFlow is a short BFS augmenting-path routine on a capacity matrix (parallel edges just add up).
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}1920static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 121int[][] cap = new int[n][n];22for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 123return maxFlow(cap, 0, n - 1);24}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Vertex splitting
A flow network limits edges, not vertices. To allow each vertex to be used by at most one path, **split** every inner vertex v into v-in and v-out joined by one edge of capacity 1. Every edge that entered v now enters v-in, and every edge that left v leaves v-out. A flow can pass through v at most once.
On the 6-vertex graph above, both routes must use vertex 3, so there is only **1** vertex-disjoint path (the “Vertex-disjoint paths: both routes cross vertex 3” preset), but 2 edge-disjoint paths. Vertex splitting is the standard trick whenever the limited resource is a node: a room, a machine, a junction.
The source and sink are not split: paths are allowed to share them. The code indexes in as 2v and out as 2v + 1.
1static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink2int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 13for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once4for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b5return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)6}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Assignment
**Assignment**: workers can each do some jobs; every worker takes at most one job and every job is done by at most one worker. Maximise the number of assigned pairs. Build a network with a source joined to every worker (capacity 1), every allowed pair as a worker-to-job edge (capacity 1), and every job joined to the sink (capacity 1). Integral flows are exactly matchings.
The preset “Assignment: 3 workers, 3 jobs” has pairs 0-0 0-1 1-0 2-1 2-2 and assigns all three. In “Assignment: no perfect matching” workers 0, 1 and 2 all compete for job 0 and only worker 2 has another option (job 1), so at most 2 pairs can be made. The two sets of vertices form a bipartite graph; Chapter 40 treats matchings in depth, including faster algorithms.
The code takes pairs {worker, job} and returns the maximum number of assignments.
1static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}2int n = workers + jobs + 2, s = 0, t = n - 1;3int[][] cap = new int[n][n];4for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job5for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker6for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;7return maxFlow(cap, s, t);8}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Resource allocation
**Resource allocation** generalises assignment to quantities: suppliers have stock, customers have demands, and each supplier-customer pair has a shipping limit. Add a **super source** joined to every supplier with capacity equal to its stock, and a **super sink** reached from every customer with capacity equal to its demand. The maximum flow is the largest total amount that can be shipped.
If the maximum flow equals the total demand, every customer can be served. If it is smaller, the minimum cut shows which group of customers (and suppliers) is the bottleneck.
The super source and sink are the standard trick for several sources or sinks. The same construction appears in the circulation lesson that follows.
1static int maxShipment(int[] supply, int[] demand, int[][] limit) { // limit[i][j]: most supplier i can send to customer j2int m = supply.length, k = demand.length, s = 0, t = m + k + 1;3int[][] cap = new int[t + 1][t + 1];4for (int i = 0; i < m; i++) cap[s][1 + i] = supply[i]; // a supplier cannot send more than it has5for (int j = 0; j < k; j++) cap[1 + m + j][t] = demand[j]; // a customer takes no more than it needs6for (int i = 0; i < m; i++) for (int j = 0; j < k; j++) cap[1 + i][1 + m + j] = limit[i][j];7return maxFlow(cap, s, t);8}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Circulation with demands
A **circulation with demands** has no single source and sink. Each vertex v has a **supply** b(v): positive means it injects that much net flow, negative means it consumes. The supplies add up to 0, and the flow on each edge must respect its capacity. Does a flow exist where every vertex's outflow minus inflow equals its supply?
Reduce it to max flow: add a super source S* with an edge of capacity b(v) to every vertex with positive supply, and a super sink T* with an edge of capacity −b(v) from every vertex with negative supply. A feasible flow exists exactly when the maximum flow from S* saturates all the supply edges, that is, equals the sum of the positive supplies.
Load “Circulation: feasible with demands” (0-1:3 0-2:2 1-3:2 2-3:3, supplies 3 0 0 -3): vertex 0 must send 3 units to vertex 3; the route through vertex 1 carries at most 2 and the route through vertex 2 at most 2, so 3 units fit, and the animation saturates 3 of 3.
1static boolean feasibleDemands(int n, int[][] edges, int[] supply) { // edges {a, b, capacity}; supply sums to 02int s = n, t = n + 1;3int[][] cap = new int[n + 2][n + 2];4for (int[] e : edges) cap[e[0]][e[1]] += e[2];5int required = 0;6for (int v = 0; v < n; v++) {7if (supply[v] > 0) { cap[s][v] = supply[v]; required += supply[v]; }8else if (supply[v] < 0) cap[v][t] = -supply[v];9}10return maxFlow(cap, s, t) == required; // every unit of supply found a demand11}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Lower bounds
Sometimes an edge must carry **at least** low units as well as at most high. Handle the lower bound by forcing it first: send low units along the edge for free. That removes low from the supply of its tail and adds it to the head's supply, and leaves a capacity of high − low for the optional part. After this adjustment the problem is a circulation with demands and no lower bounds.
In the editor write such an edge as a-b:low~high. The preset “Circulation: a lower bound makes it infeasible” (0-1:2~3 1-2:0~1, all supplies 0) forces 2 units from vertex 0 to vertex 1; vertex 1 has to pass them on but only has capacity 1 out, and nothing returns to vertex 0. The adjusted supplies need 2 units that cannot be routed, so the answer is infeasible, saturated 0 of 2.
To get an actual feasible flow, run the max flow, then add each edge's lower bound back to the flow found on its optional part.
1static boolean feasibleCirculation(int n, int[][] edges, int[] supply) { // edges {a, b, low, high}2int[] adjusted = supply.clone();3int[][] capOnly = new int[edges.length][];4for (int k = 0; k < edges.length; k++) {5int[] e = edges[k];6adjusted[e[0]] -= e[2]; // the forced units leave a for free ...7adjusted[e[1]] += e[2]; // ... and arrive at b8capOnly[k] = new int[]{e[0], e[1], e[3] - e[2]}; // only the optional part is routed9}10return feasibleDemands(n, capOnly, adjusted);11}
Modelling with flow: disjoint paths, vertex splitting, assignment, circulation
Interactive · edit the inputthe directed graph · 4 vertices · 5 edges
source
sink
Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most.
1static int maxFlow(int[][] cap, int s, int t) { // cap[u][v]: capacity, becomes the residual matrix2int n = cap.length, flow = 0;3while (true) {4int[] parent = new int[n];5Arrays.fill(parent, -1);6parent[s] = s;7Deque<Integer> queue = new ArrayDeque<>(List.of(s));8while (!queue.isEmpty() && parent[t] == -1) {9int u = queue.poll();10for (int v = 0; v < n; v++) if (parent[v] == -1 && cap[u][v] > 0) { parent[v] = u; queue.add(v); }11}12if (parent[t] == -1) return flow;13int b = Integer.MAX_VALUE;14for (int v = t; v != s; v = parent[v]) b = Math.min(b, cap[parent[v]][v]);15for (int v = t; v != s; v = parent[v]) { cap[parent[v]][v] -= b; cap[v][parent[v]] += b; }16flow += b;17}18}19static int edgeDisjointPaths(int n, int[][] edges) { // directed edges {a, b}; source 0, sink n - 120int[][] cap = new int[n][n];21for (int[] e : edges) cap[e[0]][e[1]]++; // every edge gets capacity 122return maxFlow(cap, 0, n - 1);23}24static int vertexDisjointPaths(int n, int[][] edges) { // paths may share only the source and the sink25int[][] cap = new int[2 * n][2 * n]; // vertex v is split: in = 2v, out = 2v + 126for (int v = 1; v < n - 1; v++) cap[2 * v][2 * v + 1] = 1; // the vertex itself can be used once27for (int[] e : edges) cap[2 * e[0] + 1][2 * e[1]]++; // edge: out of a, into b28return maxFlow(cap, 1, 2 * (n - 1)); // from out(source) to in(sink)29}30static int maxAssignments(int workers, int jobs, int[][] canDo) { // canDo {worker, job}31int n = workers + jobs + 2, s = 0, t = n - 1;32int[][] cap = new int[n][n];33for (int w = 0; w < workers; w++) cap[s][1 + w] = 1; // each worker takes at most one job34for (int j = 0; j < jobs; j++) cap[1 + workers + j][t] = 1; // each job is done by at most one worker35for (int[] p : canDo) cap[1 + p[0]][1 + workers + p[1]] = 1;36return maxFlow(cap, s, t);37}38// edges {a, b, low, high}; supply[v] = flow that must leave v net (negative: must arrive); returns true if a feasible flow exists39static boolean feasibleCirculation(int n, int[][] edges, int[] supply) {40int s = n, t = n + 1;41int[][] cap = new int[n + 2][n + 2];42int[] need = supply.clone(); // supply left over after the forced lower-bound flow43for (int[] e : edges) {44cap[e[0]][e[1]] += e[3] - e[2]; // only the optional part is routed45need[e[0]] -= e[2];46need[e[1]] += e[2];47}48int required = 0;49for (int v = 0; v < n; v++) {50if (need[v] > 0) { cap[s][v] = need[v]; required += need[v]; }51else if (need[v] < 0) cap[v][t] = -need[v];52}53return maxFlow(cap, s, t) == required; // every unit of surplus found a deficit54}
Live state
problem
Edge-disjoint paths
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L19 | problem=Edge-disjoint paths | Find as many paths from 0 to 3 as possible so that no edge is used twice. Each edge may be used by one path at most. |
| 2 | L20 | vertices=4 edges=5 | Give every edge capacity 1. A flow of value k from the source to the sink then splits into k paths that share no edge, and k disjoint paths give a flow of value k. |
| 3 | L22 | max_flow=2 | Maximum flow in the model network: 2. |
| 4 | L19 | return=2 paths=2 | 2 edge-disjoint paths: 0→2→3; 0→1→3. By max-flow min-cut this also equals the fewest edges whose removal disconnects 3 from 0. |
Practice ladder
Now try it yourself
Easy
- Find the Town Judge
Medium
- Is Graph Bipartite?
- Cheapest Flights Within K Stops
Advanced
- Maximum Students Taking Exam
- Distribute Repeating Integers
- Minimum Cost to Make at Least One Valid Path in a Grid
Key takeaways
- Edge-disjoint paths: unit capacities. Vertex-disjoint paths: split each inner vertex into in and out with capacity 1.
- Assignment and shipping: super source and sink with capacities for stock and demand.
- Circulations with demands and lower bounds reduce to one max flow that must saturate all the supply.