Min-cost flow
Min-cost flow asks for the cheapest way to send a given amount of flow. Successive shortest paths augments along the cheapest residual path each time; reverse arcs carry negative costs, which potentials let Dijkstra handle.
Learning goals
- Define min-cost flow and read costed edges
- Explain why reverse arcs have negative cost
- Run successive shortest paths with Bellman-Ford
- Use potentials so Dijkstra can replace Bellman-Ford
Prerequisites
A delivery company has several routes with different capacities and prices per parcel. Sending the most parcels is not enough; it wants the cheapest plan that still ships everything.
Costed edges
In a **min-cost flow** problem each edge has a capacity c and a cost w per unit of flow. The cost of a flow is the sum over edges of flow × w. Given a required amount of flow (or “as much as possible”) from s to t, find the flow of that value with the smallest total cost.
Open the animation: each edge shows flow / capacity · cost. The default network 0-1:3/1 0-2:2/2 1-2:1/1 1-3:2/3 2-3:3/1 lets 5 units through; the cheapest plan costs 17.
The representation extends the flow network by a cost per arc. The reverse arc of an edge with cost w gets cost −w, because pushing flow back along it undoes some flow and so refunds its cost.
1static class CostNet {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap, cost; // arc k and arc k ^ 1 are each other's reverse5int arcs = 0;67@SuppressWarnings("unchecked")8CostNet(int n, int maxEdges) {9this.n = n; adj = new List[n]; to = new int[2 * maxEdges]; cap = new int[2 * maxEdges]; cost = new int[2 * maxEdges];10for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();11}1213void addEdge(int u, int v, int c, int w) {14to[arcs] = v; cap[arcs] = c; cost[arcs] = w; adj[u].add(arcs++);15to[arcs] = u; cap[arcs] = 0; cost[arcs] = -w; adj[v].add(arcs++); // undoing flow gives the cost back16}17}
Min-cost flow: successive shortest augmenting paths, potentials
Interactive · edit the inputedge label: flow / capacity · cost per unit · 4 vertices · 5 edges
source
sink
residual arcs u→v : capacity @ cost
Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap, cost; // 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]; cost = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c, int w) {12to[arcs] = v; cap[arcs] = c; cost[arcs] = w; adj[u].add(arcs++);13to[arcs] = u; cap[arcs] = 0; cost[arcs] = -w; adj[v].add(arcs++); // undoing flow gives the cost back14}15}16static long[] minCostFlowBellmanFord(Net g, int s, int t, int want) { // {flow, cost}; want 0 = as much flow as possible17long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;18while (want == 0 || flow < want) {19long[] dist = new long[g.n];20Arrays.fill(dist, inf);21dist[s] = 0;22int[] via = new int[g.n];23Arrays.fill(via, -1);24for (int round = 0; round < g.n - 1; round++) { // Bellman-Ford copes with negative reverse-arc costs25boolean changed = false;26for (int u = 0; u < g.n; u++) {27if (dist[u] == inf) continue;28for (int id : g.adj[u]) {29int v = g.to[id];30if (g.cap[id] > 0 && dist[u] + g.cost[id] < dist[v]) { dist[v] = dist[u] + g.cost[id]; via[v] = id; changed = true; }31}32}33if (!changed) break;34}35if (dist[t] == inf) break; // no augmenting path is left36long push = want == 0 ? Integer.MAX_VALUE : want - flow;37for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);38for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }39flow += push;40total += push * dist[t]; // every unit of this path costs dist[t]41}42return new long[]{flow, total};43}44static long[] minCostFlowPotentials(Net g, int s, int t, int want) { // needs every original cost >= 045long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;46long[] h = new long[g.n]; // potentials: reduced cost = cost + h[u] - h[v] >= 047while (want == 0 || flow < want) {48long[] dist = new long[g.n];49Arrays.fill(dist, inf);50dist[s] = 0;51int[] via = new int[g.n];52Arrays.fill(via, -1);53PriorityQueue<long[]> pq = new PriorityQueue<>((x, y) -> Long.compare(x[0], y[0]));54pq.add(new long[]{0, s});55while (!pq.isEmpty()) { // Dijkstra on reduced costs56long[] top = pq.poll();57int u = (int) top[1];58if (top[0] > dist[u]) continue;59for (int id : g.adj[u]) {60int v = g.to[id];61long nd = dist[u] + g.cost[id] + h[u] - h[v];62if (g.cap[id] > 0 && nd < dist[v]) { dist[v] = nd; via[v] = id; pq.add(new long[]{nd, v}); }63}64}65if (dist[t] == inf) break;66for (int v = 0; v < g.n; v++) if (dist[v] < inf) h[v] += dist[v]; // keep reduced costs non-negative next round67long push = want == 0 ? Integer.MAX_VALUE : want - flow;68for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);69for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }70flow += push;71total += push * (h[t] - h[s]); // h[t] - h[s] is the true cost of the path72}73return new long[]{flow, total};74}
Live state
n
4
edges
5
wanted
max
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=4 edges=5 wanted=max | Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost. |
| 2 | L36 | path=0→2→3 path_cost=3 push=2 | Cheapest residual path: 0→2→3, cost 3 per unit. Bottleneck 2. |
| 3 | L40 | flow=2 cost=6 | Push 2 units: 2 × 3 = 6. Total flow 2, total cost 6. |
| 4 | L36 | path=0→1→2→3 path_cost=3 push=1 | Cheapest residual path: 0→1→2→3, cost 3 per unit. Bottleneck 1. |
| 5 | L40 | flow=3 cost=9 | Push 1 unit: 1 × 3 = 3. Total flow 3, total cost 9. |
| 6 | L36 | path=0→1→3 path_cost=4 push=2 | Cheapest residual path: 0→1→3, cost 4 per unit. Bottleneck 2. |
| 7 | L40 | flow=5 cost=17 | Push 2 units: 2 × 4 = 8. Total flow 5, total cost 17. |
| 8 | L35 | flow=5 cost=17 | No residual path from 0 to 3 exists: the network cannot carry more flow. |
| 9 | L42 | return=17 flow=5 augmentations=3 | Minimum cost of 5 units of flow: 17, found with 3 augmentations. Every augmentation used a cheapest path, and the path costs never decrease from one round to the next. |
Residual negative costs
Negative arc costs appear even if all original costs are non-negative. On the “A negative reverse arc is used” preset (0-1:1/1 0-2:1/5 1-2:1/1 1-3:1/5 2-3:1/1) the first cheapest path is 0→1→2→3 with cost 3. For the second unit the cheapest residual path is 0→2→1→3, and its middle step 2→1 is a reverse arc of cost −1: it cancels the unit sent over 1→2 and reroutes it through 1→3. The path costs 5 − 1 + 5 = 9, and the total for 2 units is 12.
Because costs can be negative, shortest paths in the residual graph need Bellman-Ford (Chapter 35). There is never a negative cycle in the residual graph as long as the current flow is a minimum-cost flow for its value; this invariant is exactly what the algorithm maintains.
Bellman-Ford costs O(V·E) per augmentation. The method below is the complete algorithm with that choice.
1static long[] minCostFlowBellmanFord(CostNet g, int s, int t, int want) { // {flow, cost}; want 0 = as much flow as possible2long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;3while (want == 0 || flow < want) {4long[] dist = new long[g.n];5Arrays.fill(dist, inf);6dist[s] = 0;7int[] via = new int[g.n];8Arrays.fill(via, -1);9for (int round = 0; round < g.n - 1; round++) { // Bellman-Ford copes with negative reverse-arc costs10boolean changed = false;11for (int u = 0; u < g.n; u++) {12if (dist[u] == inf) continue;13for (int id : g.adj[u]) {14int v = g.to[id];15if (g.cap[id] > 0 && dist[u] + g.cost[id] < dist[v]) { dist[v] = dist[u] + g.cost[id]; via[v] = id; changed = true; }16}17}18if (!changed) break;19}20if (dist[t] == inf) break; // no augmenting path is left21long push = want == 0 ? Integer.MAX_VALUE : want - flow;22for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);23for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }24flow += push;25total += push * dist[t]; // every unit of this path costs dist[t]26}27return new long[]{flow, total};28}
Min-cost flow: successive shortest augmenting paths, potentials
Interactive · edit the inputedge label: flow / capacity · cost per unit · 4 vertices · 5 edges
source
sink
residual arcs u→v : capacity @ cost
Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap, cost; // 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]; cost = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c, int w) {12to[arcs] = v; cap[arcs] = c; cost[arcs] = w; adj[u].add(arcs++);13to[arcs] = u; cap[arcs] = 0; cost[arcs] = -w; adj[v].add(arcs++); // undoing flow gives the cost back14}15}16static long[] minCostFlowBellmanFord(Net g, int s, int t, int want) { // {flow, cost}; want 0 = as much flow as possible17long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;18while (want == 0 || flow < want) {19long[] dist = new long[g.n];20Arrays.fill(dist, inf);21dist[s] = 0;22int[] via = new int[g.n];23Arrays.fill(via, -1);24for (int round = 0; round < g.n - 1; round++) { // Bellman-Ford copes with negative reverse-arc costs25boolean changed = false;26for (int u = 0; u < g.n; u++) {27if (dist[u] == inf) continue;28for (int id : g.adj[u]) {29int v = g.to[id];30if (g.cap[id] > 0 && dist[u] + g.cost[id] < dist[v]) { dist[v] = dist[u] + g.cost[id]; via[v] = id; changed = true; }31}32}33if (!changed) break;34}35if (dist[t] == inf) break; // no augmenting path is left36long push = want == 0 ? Integer.MAX_VALUE : want - flow;37for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);38for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }39flow += push;40total += push * dist[t]; // every unit of this path costs dist[t]41}42return new long[]{flow, total};43}44static long[] minCostFlowPotentials(Net g, int s, int t, int want) { // needs every original cost >= 045long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;46long[] h = new long[g.n]; // potentials: reduced cost = cost + h[u] - h[v] >= 047while (want == 0 || flow < want) {48long[] dist = new long[g.n];49Arrays.fill(dist, inf);50dist[s] = 0;51int[] via = new int[g.n];52Arrays.fill(via, -1);53PriorityQueue<long[]> pq = new PriorityQueue<>((x, y) -> Long.compare(x[0], y[0]));54pq.add(new long[]{0, s});55while (!pq.isEmpty()) { // Dijkstra on reduced costs56long[] top = pq.poll();57int u = (int) top[1];58if (top[0] > dist[u]) continue;59for (int id : g.adj[u]) {60int v = g.to[id];61long nd = dist[u] + g.cost[id] + h[u] - h[v];62if (g.cap[id] > 0 && nd < dist[v]) { dist[v] = nd; via[v] = id; pq.add(new long[]{nd, v}); }63}64}65if (dist[t] == inf) break;66for (int v = 0; v < g.n; v++) if (dist[v] < inf) h[v] += dist[v]; // keep reduced costs non-negative next round67long push = want == 0 ? Integer.MAX_VALUE : want - flow;68for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);69for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }70flow += push;71total += push * (h[t] - h[s]); // h[t] - h[s] is the true cost of the path72}73return new long[]{flow, total};74}
Live state
n
4
edges
5
wanted
max
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=4 edges=5 wanted=max | Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost. |
| 2 | L36 | path=0→2→3 path_cost=3 push=2 | Cheapest residual path: 0→2→3, cost 3 per unit. Bottleneck 2. |
| 3 | L40 | flow=2 cost=6 | Push 2 units: 2 × 3 = 6. Total flow 2, total cost 6. |
| 4 | L36 | path=0→1→2→3 path_cost=3 push=1 | Cheapest residual path: 0→1→2→3, cost 3 per unit. Bottleneck 1. |
| 5 | L40 | flow=3 cost=9 | Push 1 unit: 1 × 3 = 3. Total flow 3, total cost 9. |
| 6 | L36 | path=0→1→3 path_cost=4 push=2 | Cheapest residual path: 0→1→3, cost 4 per unit. Bottleneck 2. |
| 7 | L40 | flow=5 cost=17 | Push 2 units: 2 × 4 = 8. Total flow 5, total cost 17. |
| 8 | L35 | flow=5 cost=17 | No residual path from 0 to 3 exists: the network cannot carry more flow. |
| 9 | L42 | return=17 flow=5 augmentations=3 | Minimum cost of 5 units of flow: 17, found with 3 augmentations. Every augmentation used a cheapest path, and the path costs never decrease from one round to the next. |
Potentials
To use the faster Dijkstra we need non-negative arc costs. Keep a **potential** h[v] for every vertex (a previous shortest distance) and replace each arc cost by its **reduced cost** cost(u, v) + h[u] − h[v]. Any path from s to t changes by the same constant h[s] − h[t], so shortest paths stay shortest, and if the potentials are shortest distances then every residual arc has reduced cost ≥ 0.
After each Dijkstra run add the distances to the potentials: h[v] += dist[v] for reachable v. The reduced costs along the path just used become 0, and the new reverse arcs have reduced cost 0 as well, so the invariant is kept. The true cost of the path is h[t] − h[s]. The animation's second method (“potentials and Dijkstra”) throws an error if it ever meets a negative reduced cost, and the test suite never triggers it.
Initial potentials of 0 are valid because the original costs are non-negative. With negative original costs you would first run Bellman-Ford once to initialise them.
1static long[] minCostFlowPotentials(CostNet g, int s, int t, int want) { // needs every original cost >= 02long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;3long[] h = new long[g.n]; // potentials: reduced cost = cost + h[u] - h[v] >= 04while (want == 0 || flow < want) {5long[] dist = new long[g.n];6Arrays.fill(dist, inf);7dist[s] = 0;8int[] via = new int[g.n];9Arrays.fill(via, -1);10PriorityQueue<long[]> pq = new PriorityQueue<>((x, y) -> Long.compare(x[0], y[0]));11pq.add(new long[]{0, s});12while (!pq.isEmpty()) { // Dijkstra on reduced costs13long[] top = pq.poll();14int u = (int) top[1];15if (top[0] > dist[u]) continue;16for (int id : g.adj[u]) {17int v = g.to[id];18long nd = dist[u] + g.cost[id] + h[u] - h[v];19if (g.cap[id] > 0 && nd < dist[v]) { dist[v] = nd; via[v] = id; pq.add(new long[]{nd, v}); }20}21}22if (dist[t] == inf) break;23for (int v = 0; v < g.n; v++) if (dist[v] < inf) h[v] += dist[v]; // keep reduced costs non-negative next round24long push = want == 0 ? Integer.MAX_VALUE : want - flow;25for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);26for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }27flow += push;28total += push * (h[t] - h[s]); // h[t] - h[s] is the true cost of the path29}30return new long[]{flow, total};31}
Min-cost flow: successive shortest augmenting paths, potentials
Interactive · edit the inputedge label: flow / capacity · cost per unit · 4 vertices · 5 edges
source
sink
residual arcs u→v : capacity @ cost
Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap, cost; // 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]; cost = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c, int w) {12to[arcs] = v; cap[arcs] = c; cost[arcs] = w; adj[u].add(arcs++);13to[arcs] = u; cap[arcs] = 0; cost[arcs] = -w; adj[v].add(arcs++); // undoing flow gives the cost back14}15}16static long[] minCostFlowBellmanFord(Net g, int s, int t, int want) { // {flow, cost}; want 0 = as much flow as possible17long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;18while (want == 0 || flow < want) {19long[] dist = new long[g.n];20Arrays.fill(dist, inf);21dist[s] = 0;22int[] via = new int[g.n];23Arrays.fill(via, -1);24for (int round = 0; round < g.n - 1; round++) { // Bellman-Ford copes with negative reverse-arc costs25boolean changed = false;26for (int u = 0; u < g.n; u++) {27if (dist[u] == inf) continue;28for (int id : g.adj[u]) {29int v = g.to[id];30if (g.cap[id] > 0 && dist[u] + g.cost[id] < dist[v]) { dist[v] = dist[u] + g.cost[id]; via[v] = id; changed = true; }31}32}33if (!changed) break;34}35if (dist[t] == inf) break; // no augmenting path is left36long push = want == 0 ? Integer.MAX_VALUE : want - flow;37for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);38for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }39flow += push;40total += push * dist[t]; // every unit of this path costs dist[t]41}42return new long[]{flow, total};43}44static long[] minCostFlowPotentials(Net g, int s, int t, int want) { // needs every original cost >= 045long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;46long[] h = new long[g.n]; // potentials: reduced cost = cost + h[u] - h[v] >= 047while (want == 0 || flow < want) {48long[] dist = new long[g.n];49Arrays.fill(dist, inf);50dist[s] = 0;51int[] via = new int[g.n];52Arrays.fill(via, -1);53PriorityQueue<long[]> pq = new PriorityQueue<>((x, y) -> Long.compare(x[0], y[0]));54pq.add(new long[]{0, s});55while (!pq.isEmpty()) { // Dijkstra on reduced costs56long[] top = pq.poll();57int u = (int) top[1];58if (top[0] > dist[u]) continue;59for (int id : g.adj[u]) {60int v = g.to[id];61long nd = dist[u] + g.cost[id] + h[u] - h[v];62if (g.cap[id] > 0 && nd < dist[v]) { dist[v] = nd; via[v] = id; pq.add(new long[]{nd, v}); }63}64}65if (dist[t] == inf) break;66for (int v = 0; v < g.n; v++) if (dist[v] < inf) h[v] += dist[v]; // keep reduced costs non-negative next round67long push = want == 0 ? Integer.MAX_VALUE : want - flow;68for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);69for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }70flow += push;71total += push * (h[t] - h[s]); // h[t] - h[s] is the true cost of the path72}73return new long[]{flow, total};74}
Live state
n
4
edges
5
wanted
max
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=4 edges=5 wanted=max | Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost. |
| 2 | L36 | path=0→2→3 path_cost=3 push=2 | Cheapest residual path: 0→2→3, cost 3 per unit. Bottleneck 2. |
| 3 | L40 | flow=2 cost=6 | Push 2 units: 2 × 3 = 6. Total flow 2, total cost 6. |
| 4 | L36 | path=0→1→2→3 path_cost=3 push=1 | Cheapest residual path: 0→1→2→3, cost 3 per unit. Bottleneck 1. |
| 5 | L40 | flow=3 cost=9 | Push 1 unit: 1 × 3 = 3. Total flow 3, total cost 9. |
| 6 | L36 | path=0→1→3 path_cost=4 push=2 | Cheapest residual path: 0→1→3, cost 4 per unit. Bottleneck 2. |
| 7 | L40 | flow=5 cost=17 | Push 2 units: 2 × 4 = 8. Total flow 5, total cost 17. |
| 8 | L35 | flow=5 cost=17 | No residual path from 0 to 3 exists: the network cannot carry more flow. |
| 9 | L42 | return=17 flow=5 augmentations=3 | Minimum cost of 5 units of flow: 17, found with 3 augmentations. Every augmentation used a cheapest path, and the path costs never decrease from one round to the next. |
Successive shortest augmenting path
**Successive shortest augmenting paths** is the whole algorithm: repeatedly find a cheapest path in the residual graph and push its bottleneck (or only as many units as still wanted). Path costs never decrease from one round to the next, and the flow after k units is a minimum-cost flow of value k.
On the default network the rounds cost 3, 3 and 4 per unit: two units over 0→2→3 (cost 3 each, 6), one unit over 0→1→2→3 (3), and two units over 0→1→3 (4 each, 8), a total of 17 for 5 units. Ask for only 3 units with the “Only 3 units wanted” preset and the cost is 9. Ask for more than the network allows (“More flow wanted than possible”): the algorithm stops with the maximum flow 2 and cost 6.
The method builds the network from edge records and returns the cost, or −1 if fewer than k units fit.
1static long minCostForUnits(int n, int[][] edges, int s, int t, int k) { // edges {from, to, capacity, cost}; -1 if k units do not fit2CostNet g = new CostNet(n, edges.length);3for (int[] e : edges) g.addEdge(e[0], e[1], e[2], e[3]);4long[] r = minCostFlowBellmanFord(g, s, t, k);5return r[0] < k ? -1 : r[1];6}
Worst
O(F · V · E) with Bellman-Ford, O(F · E log V) with potentials and a heap (F = units of flow)
Space
O(V + E)
Min-cost flow: successive shortest augmenting paths, potentials
Interactive · edit the inputedge label: flow / capacity · cost per unit · 4 vertices · 5 edges
source
sink
residual arcs u→v : capacity @ cost
Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap, cost; // 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]; cost = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c, int w) {12to[arcs] = v; cap[arcs] = c; cost[arcs] = w; adj[u].add(arcs++);13to[arcs] = u; cap[arcs] = 0; cost[arcs] = -w; adj[v].add(arcs++); // undoing flow gives the cost back14}15}16static long[] minCostFlowBellmanFord(Net g, int s, int t, int want) { // {flow, cost}; want 0 = as much flow as possible17long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;18while (want == 0 || flow < want) {19long[] dist = new long[g.n];20Arrays.fill(dist, inf);21dist[s] = 0;22int[] via = new int[g.n];23Arrays.fill(via, -1);24for (int round = 0; round < g.n - 1; round++) { // Bellman-Ford copes with negative reverse-arc costs25boolean changed = false;26for (int u = 0; u < g.n; u++) {27if (dist[u] == inf) continue;28for (int id : g.adj[u]) {29int v = g.to[id];30if (g.cap[id] > 0 && dist[u] + g.cost[id] < dist[v]) { dist[v] = dist[u] + g.cost[id]; via[v] = id; changed = true; }31}32}33if (!changed) break;34}35if (dist[t] == inf) break; // no augmenting path is left36long push = want == 0 ? Integer.MAX_VALUE : want - flow;37for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);38for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }39flow += push;40total += push * dist[t]; // every unit of this path costs dist[t]41}42return new long[]{flow, total};43}44static long[] minCostFlowPotentials(Net g, int s, int t, int want) { // needs every original cost >= 045long flow = 0, total = 0, inf = Long.MAX_VALUE / 2;46long[] h = new long[g.n]; // potentials: reduced cost = cost + h[u] - h[v] >= 047while (want == 0 || flow < want) {48long[] dist = new long[g.n];49Arrays.fill(dist, inf);50dist[s] = 0;51int[] via = new int[g.n];52Arrays.fill(via, -1);53PriorityQueue<long[]> pq = new PriorityQueue<>((x, y) -> Long.compare(x[0], y[0]));54pq.add(new long[]{0, s});55while (!pq.isEmpty()) { // Dijkstra on reduced costs56long[] top = pq.poll();57int u = (int) top[1];58if (top[0] > dist[u]) continue;59for (int id : g.adj[u]) {60int v = g.to[id];61long nd = dist[u] + g.cost[id] + h[u] - h[v];62if (g.cap[id] > 0 && nd < dist[v]) { dist[v] = nd; via[v] = id; pq.add(new long[]{nd, v}); }63}64}65if (dist[t] == inf) break;66for (int v = 0; v < g.n; v++) if (dist[v] < inf) h[v] += dist[v]; // keep reduced costs non-negative next round67long push = want == 0 ? Integer.MAX_VALUE : want - flow;68for (int v = t; v != s; v = g.to[via[v] ^ 1]) push = Math.min(push, g.cap[via[v]]);69for (int v = t; v != s; v = g.to[via[v] ^ 1]) { g.cap[via[v]] -= push; g.cap[via[v] ^ 1] += push; }70flow += push;71total += push * (h[t] - h[s]); // h[t] - h[s] is the true cost of the path72}73return new long[]{flow, total};74}
Live state
n
4
edges
5
wanted
max
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=4 edges=5 wanted=max | Send as much flow as possible from 0 to 3 at the lowest total cost. Each edge shows flow / capacity and its cost per unit. Repeatedly take the cheapest residual path from 0 to 3; pushing along reverse arcs undoes flow and gives its cost back, which is why reverse arcs have negative cost. |
| 2 | L36 | path=0→2→3 path_cost=3 push=2 | Cheapest residual path: 0→2→3, cost 3 per unit. Bottleneck 2. |
| 3 | L40 | flow=2 cost=6 | Push 2 units: 2 × 3 = 6. Total flow 2, total cost 6. |
| 4 | L36 | path=0→1→2→3 path_cost=3 push=1 | Cheapest residual path: 0→1→2→3, cost 3 per unit. Bottleneck 1. |
| 5 | L40 | flow=3 cost=9 | Push 1 unit: 1 × 3 = 3. Total flow 3, total cost 9. |
| 6 | L36 | path=0→1→3 path_cost=4 push=2 | Cheapest residual path: 0→1→3, cost 4 per unit. Bottleneck 2. |
| 7 | L40 | flow=5 cost=17 | Push 2 units: 2 × 4 = 8. Total flow 5, total cost 17. |
| 8 | L35 | flow=5 cost=17 | No residual path from 0 to 3 exists: the network cannot carry more flow. |
| 9 | L42 | return=17 flow=5 augmentations=3 | Minimum cost of 5 units of flow: 17, found with 3 augmentations. Every augmentation used a cheapest path, and the path costs never decrease from one round to the next. |
Practice ladder
Now try it yourself
Easy
- Min Cost Climbing Stairs
Medium
- Cheapest Flights Within K Stops
- Network Delay Time
Advanced
- Minimum Cost to Make at Least One Valid Path in a Grid
- Minimum Number of Lines to Cover Points
Key takeaways
- Min-cost flow: send a required flow at minimum total cost; reverse arcs have negative cost.
- Successive shortest paths augments along the cheapest residual path; path costs never decrease.
- Potentials make every reduced cost non-negative so Dijkstra replaces Bellman-Ford.