Logo
Published on

Network Flows - Part 6

Authors
  • avatar
    Name
    Till Heller
    Twitter

In the previous parts, we focused on pushing as much flow as possible from source to sink. But in many real-world scenarios, not all routes are created equal. Shipping goods through one city might be cheap, while routing them through another might be expensive. When we care about both how much we send and what it costs, we need the minimum-cost flow problem — a beautiful generalization that ties together maximum flow, shortest paths, and linear programming.

The Problem

Let's extend our flow networks with a new ingredient: a cost for each arc.

Definition 12. A minimum-cost flow network is a tuple N=(D,s,t,c,w)N = (D, s, t, c, w), where (D,s,t,c)(D, s, t, c) is a flow network as before, and w:ARw: A \rightarrow \mathbb{R} is a cost function assigning a cost w(a)w(a) to each arc aAa \in A. The cost w(a)w(a) represents the cost per unit of flow on arc aa.

Definition 13. The cost of a flow ff is cost(f)=aAw(a)f(a)\text{cost}(f) = \sum_{a \in A} w(a) \cdot f(a).

Now we can state the problem precisely.

Minimum-Cost Maximum Flow Problem. Given a minimum-cost flow network N=(D,s,t,c,w)N = (D, s, t, c, w), find a maximum flow ff that minimizes cost(f)\text{cost}(f) among all maximum flows.

Think of the supply chain from Part 1: we want to transport as much product as possible (maximum flow), but among all ways of doing so, we want the cheapest one (minimum cost). The two objectives are lexicographic — first maximize flow, then minimize cost.

There is also a more general formulation where we fix a desired flow value dd and ask for the cheapest flow of value exactly dd. The maximum-flow version is the special case where d=fd = |f^*|.

Let's set up a running example. Consider the network with vertices {s,a,b,t}\{s, a, b, t\}, arcs and capacities c(s,a)=4c(s,a) = 4, c(s,b)=3c(s,b) = 3, c(a,b)=2c(a,b) = 2, c(a,t)=3c(a,t) = 3, c(b,t)=5c(b,t) = 5, and costs w(s,a)=1w(s,a) = 1, w(s,b)=4w(s,b) = 4, w(a,b)=2w(a,b) = 2, w(a,t)=3w(a,t) = 3, w(b,t)=1w(b,t) = 1.

The cheapest route from ss to tt goes through aa and then directly to tt (cost 1+3=41 + 3 = 4 per unit), while the route through bb costs more per unit (4+1=54 + 1 = 5) but has more capacity. The challenge is finding the right balance.

mincost-network

Residual Costs

In Part 3, we introduced the residual graph to capture where more flow can be sent. For minimum-cost flows, we need to extend the residual graph with costs.

Definition 14. Given a flow ff in a minimum-cost flow network, the residual cost of an arc in the residual graph DfD_f is defined as:

  • For a forward arc (u,v)(u,v): wf(u,v)=w(u,v)w_f(u,v) = w(u,v). Sending more flow along this arc costs w(u,v)w(u,v) per unit.
  • For a backward arc (v,u)(v,u): wf(v,u)=w(u,v)w_f(v,u) = -w(u,v). Cancelling flow on (u,v)(u,v) saves w(u,v)w(u,v) per unit, so the residual cost is negative.

The backward arc costs are the key insight. When we "undo" flow on an arc, we recover the cost we previously paid. This means that a negative-cost cycle in the residual graph represents a way to reroute existing flow to reduce the total cost without changing the flow value — we push flow around the cycle, and the negative total cost means we save money.

Negative Cycle Optimality

This observation leads to a clean characterization of optimal flows.

Theorem 8 (Negative Cycle Optimality). A feasible flow ff of a given value is minimum-cost if and only if the residual graph DfD_f contains no negative-cost directed cycle.

Proof. For the "only if" direction: suppose DfD_f contains a directed cycle CC with negative total cost wf(C)=(u,v)Cwf(u,v)<0w_f(C) = \sum_{(u,v) \in C} w_f(u,v) < 0. Let δ=min(u,v)Ccf(u,v)>0\delta = \min_{(u,v) \in C} c_f(u,v) > 0 be the minimum residual capacity along CC. We can push δ\delta units of flow around CC. Since CC is a cycle, the flow value doesn't change (whatever enters a vertex also leaves it), but the cost decreases by δwf(C)>0\delta \cdot |w_f(C)| > 0. So ff was not minimum-cost.

For the "if" direction, the argument is more involved and relies on linear programming duality. The idea is that if no negative-cost cycle exists, we can construct a set of vertex potentials (dual variables) that certify optimality via complementary slackness1. We omit the full proof here.

The Cycle-Canceling Algorithm

The negative cycle optimality condition immediately suggests an algorithm: start with any maximum flow and repeatedly cancel negative-cost cycles until none remain.

Cycle-Canceling Algorithm:

  1. Find a maximum flow ff (using Edmonds-Karp or any max-flow algorithm).
  2. Build the residual graph DfD_f with residual costs.
  3. Search for a negative-cost directed cycle CC in DfD_f.
  4. If no negative cycle exists, stop — ff is a minimum-cost maximum flow.
  5. Otherwise, compute δ=min(u,v)Ccf(u,v)\delta = \min_{(u,v) \in C} c_f(u,v) and push δ\delta units around CC.
  6. Go to step 2.

Finding a negative cycle can be done using the Bellman-Ford algorithm, which detects negative cycles in O(nm)O(nm) time.

Observation 3. Each iteration of the cycle-canceling algorithm strictly decreases the cost of the flow while preserving the flow value.

For integer capacities and costs, the algorithm terminates because the cost is an integer that decreases by at least 11 in each iteration and is bounded below. However, the number of iterations can be large — proportional to the total cost, which may be exponential in the input size. More sophisticated cycle selection rules (such as always canceling the minimum mean cost cycle) lead to polynomial-time variants, but we won't go into those details here.

The Successive Shortest Paths Algorithm

There is an alternative approach that builds the minimum-cost maximum flow from scratch, rather than first finding any maximum flow and then repairing it. The idea is simple: send flow along the cheapest possible route, one unit at a time.

Successive Shortest Paths Algorithm:

  1. Initialize f(a)=0f(a) = 0 for every arc aa.
  2. Build the residual graph DfD_f with residual costs.
  3. Find a shortest path (minimum total cost) from ss to tt in DfD_f using the residual costs as edge weights.
  4. If no ss-tt-path exists, stop — ff is a minimum-cost maximum flow.
  5. Otherwise, let PP be the shortest path and δ=min(u,v)Pcf(u,v)\delta = \min_{(u,v) \in P} c_f(u,v).
  6. Push δ\delta units of flow along PP (same as in Ford-Fulkerson).
  7. Go to step 2.

This algorithm augments flow along the cheapest augmenting path instead of the shortest (fewest arcs) augmenting path. Each augmentation simultaneously increases the flow value and ensures minimum cost for the current flow value.

Theorem 9. The successive shortest paths algorithm maintains the invariant that after each augmentation, the current flow is a minimum-cost flow among all flows of the same value.

Proof sketch. Initially, the zero flow is trivially minimum-cost (it's the only flow of value 00). Suppose the current flow ff with value f|f| is minimum-cost. We augment along a shortest path PP in the residual graph. If the resulting flow ff' were not minimum-cost for value f|f'|, there would be a cheaper flow ff'' of the same value. The difference between ff' and ff'' can be decomposed into cycles in the residual graph of ff'. But the shortest-path augmentation guarantees that no negative-cost cycle exists in DfD_{f'}2 — a contradiction.

There is one subtlety: the residual graph may contain negative-cost arcs (the backward arcs), so we cannot directly use Dijkstra's algorithm for the shortest-path computation. However, we can use Bellman-Ford (O(nm)O(nm) per iteration), or — more cleverly — maintain vertex potentials to transform all residual costs to non-negative values, enabling Dijkstra's algorithm (O(m+nlogn)O(m + n \log n) per iteration with a Fibonacci heap).

With integer capacities, each augmentation sends at least 11 unit, so the algorithm performs at most f|f^*| augmentations. Using Bellman-Ford, the total running time is O(nmf)O(nm \cdot |f^*|). With the potential-based Dijkstra approach, each iteration costs O(m+nlogn)O(m + n \log n), giving O((m+nlogn)f)O((m + n \log n) \cdot |f^*|).

A Worked Example

Let's trace the successive shortest paths algorithm on our running example: c(s,a)=4c(s,a) = 4, c(s,b)=3c(s,b) = 3, c(a,b)=2c(a,b) = 2, c(a,t)=3c(a,t) = 3, c(b,t)=5c(b,t) = 5, and costs w(s,a)=1w(s,a) = 1, w(s,b)=4w(s,b) = 4, w(a,b)=2w(a,b) = 2, w(a,t)=3w(a,t) = 3, w(b,t)=1w(b,t) = 1.

Iteration 1. We find the shortest (cheapest) path from ss to tt. The candidates:

  • sats \to a \to t: cost 1+3=41 + 3 = 4, bottleneck min(4,3)=3\min(4, 3) = 3.
  • sabts \to a \to b \to t: cost 1+2+1=41 + 2 + 1 = 4, bottleneck min(4,2,5)=2\min(4, 2, 5) = 2.
  • sbts \to b \to t: cost 4+1=54 + 1 = 5, bottleneck min(3,5)=3\min(3, 5) = 3.

Both sats \to a \to t and sabts \to a \to b \to t have cost 44. Let's pick sats \to a \to t with bottleneck 33. We push 33 units: f(s,a)=3f(s,a) = 3, f(a,t)=3f(a,t) = 3. Flow value: 33. Cost: 31+33=123 \cdot 1 + 3 \cdot 3 = 12.

Iteration 2. Residual graph: forward arcs (s,a)(s,a) with capacity 11 and cost 11, (s,b)(s,b) with capacity 33 and cost 44, (a,b)(a,b) with capacity 22 and cost 22, (b,t)(b,t) with capacity 55 and cost 11; backward arcs (a,s)(a,s) with capacity 33 and cost 1-1, (t,a)(t,a) with capacity 33 and cost 3-3.

Cheapest path from ss to tt:

  • sabts \to a \to b \to t: cost 1+2+1=41 + 2 + 1 = 4, bottleneck min(1,2,5)=1\min(1, 2, 5) = 1.
  • sbts \to b \to t: cost 4+1=54 + 1 = 5, bottleneck min(3,5)=3\min(3, 5) = 3.

We pick sabts \to a \to b \to t with bottleneck 11. Push 11 unit: f(s,a)=4f(s,a) = 4, f(a,b)=1f(a,b) = 1, f(b,t)=1f(b,t) = 1. Flow value: 44. Cost: 12+14=1612 + 1 \cdot 4 = 16.

Iteration 3. Now (s,a)(s,a) is saturated. Residual forward arcs from ss: only (s,b)(s,b) with capacity 33 and cost 44.

Cheapest path: sbts \to b \to t with cost 4+1=54 + 1 = 5, bottleneck min(3,4)=3\min(3, 4) = 3. Push 33 units: f(s,b)=3f(s,b) = 3, f(b,t)=4f(b,t) = 4. Flow value: 77. Cost: 16+35=3116 + 3 \cdot 5 = 31.

Iteration 4. Residual forward arcs from ss: none (both (s,a)(s,a) and (s,b)(s,b) are saturated). No ss-tt-path exists. The algorithm terminates.

The minimum-cost maximum flow has value 77 and cost 3131. The final flow is f(s,a)=4f(s,a) = 4, f(s,b)=3f(s,b) = 3, f(a,b)=1f(a,b) = 1, f(a,t)=3f(a,t) = 3, f(b,t)=4f(b,t) = 4.

Notice how the algorithm prioritized the cheap route through aa before using the more expensive route through bb. It also used the intermediate arc (a,b)(a,b) to squeeze one extra unit through the cheaper part of the network. This is the successive shortest paths algorithm at work: each unit of flow is sent along the currently cheapest route.

successive-shortest-paths

Looking Ahead

With minimum-cost flows, we've reached the most general problem in this series. In the next and final part, we'll explore applications of minimum-cost flows — from transportation and assignment problems to minimum-weight matchings — and reflect on how the entire network flow framework connects the topics we've studied.

Footnotes

  1. The connection to linear programming runs deep. The minimum-cost flow problem is a linear program, and the absence of negative residual cycles corresponds to dual feasibility. This duality is also the reason minimum-cost flow algorithms are closely related to shortest-path algorithms.

  2. This is the key insight: augmenting along a shortest path in the residual graph preserves the negative-cycle-free property. The proof uses the fact that shortest-path distances serve as vertex potentials that make all residual costs non-negative — the same reduced-cost technique used in Johnson's algorithm for all-pairs shortest paths.