Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Graphs

Ford–Fulkerson, residual graphs and reverse edges

Lesson · 13 min read · Graphs · Maximum flow and minimum cut

Push flow along any path with spare capacity, and let later paths undo earlier choices through reverse edges.

Greedy is not enough

The natural idea is to find any path from s to t with spare capacity on every edge, push as much as the tightest edge allows, and repeat. It can get stuck. Below, every capacity is 1. If the first path chosen is s → a → b → t, no second path exists among the remaining spare capacities, yet the maximum flow is 2 (s → a → t and s → b → t).

1/10/11/10/11/1sabt
After pushing along s → a → b → t, the edges a→b and b→t are full and greedy stops at 1.

The residual graph

The fix is to allow later paths to undo earlier flow. For every edge u → v carrying flow f, the residual graph contains:

  • a forward edge u → v with the capacity still unused, c − f;
  • a reverse edge v → u with capacity f: pushing along it cancels some of the flow on u → v.

An augmenting path is a path from s to t in the residual graph. In the trap above, s → b → a → t is one: it uses the reverse edge b → a, which cancels the unit on a → b and reroutes it. The result is the two disjoint paths, flow 2.

The poor first path, then an augmenting path that travels the reverse edge b → a.

  1. Step 1 / 4
    0/10/10/10/10/1sabt
    total0

    Every edge is labelled flow/capacity, all flows 0. Look for a path from s to t with spare capacity on every step.

  2. Step 2 / 4
    1/10/11/10/11/1sabt
    total1

    Augmenting path s → a → b → t, bottleneck 1: push 1 more unit. Total flow 1.

  3. Step 3 / 4
    1/11/10/11/11/1sabt
    total2

    Augmenting path s → b → a → t, bottleneck 1: push 1 more unit. The step b→a runs against existing flow: it travels a reverse edge, cancelling flow on a→b and sending it elsewhere. Total flow 2.

  4. Step 4 / 4
    1/11/10/11/11/1sabt
    total2

    No augmenting path is left: the maximum flow is 2. The nodes still reachable from s through spare capacity (shaded) form one side of a minimum cut; the cut edges (red) are full and their capacities add to 2.

carrying flowflow cancelled through a reverse edge

The algorithm

Ford–Fulkerson

maxFlow(graph, s, t)

  1. Start with zero flow on every edge.
  2. While the residual graph has a path from s to t: let b be the smallest residual capacity on it (the bottleneck).
  3. Push b along the path: add b to each forward edge used, subtract b from the flow on each edge whose reverse was used.
  4. Return the total pushed.

Which path to take matters for speed. With arbitrary paths and large capacities the number of rounds can be as large as the flow value itself. Edmonds–Karp always takes a shortest augmenting path, found by breadth-first search, which bounds the number of rounds by O(n · m) whatever the capacities.

Edmonds–Karp, step by step

On the network from the previous lesson. Each frame pushes along the shortest augmenting path; labels show flow/capacity.

Edmonds–Karp: breadth-first search for each augmenting path, then the minimum cut it leaves behind.

  1. Step 1 / 6
    0/100/100/40/20/80/90/60/100/10sacbdt
    total0

    Every edge is labelled flow/capacity, all flows 0. Look for a path from s to t with spare capacity on every step.

  2. Step 2 / 6
    4/100/104/40/20/80/90/64/100/10sacbdt
    total4

    Augmenting path s → a → b → t, bottleneck 4: push 4 more units. Total flow 4.

  3. Step 3 / 6
    10/100/104/40/26/80/90/64/106/10sacbdt
    total10

    Augmenting path s → a → d → t, bottleneck 6: push 6 more units. Total flow 10.

  4. Step 4 / 6
    10/104/104/40/26/84/90/64/1010/10sacbdt
    total14

    Augmenting path s → c → d → t, bottleneck 4: push 4 more units. Total flow 14.

  5. Step 5 / 6
    10/109/104/40/26/89/95/69/1010/10sacbdt
    total19

    Augmenting path s → c → d → b → t, bottleneck 5: push 5 more units. Total flow 19.

  6. Step 6 / 6
    10/109/104/40/26/89/95/69/1010/10sacbdt
    total19

    No augmenting path is left: the maximum flow is 19. The nodes still reachable from s through spare capacity (shaded) form one side of a minimum cut; the cut edges (red) are full and their capacities add to 19.

carrying flowreachable from s at the endreverse edge used

The last frame is the theorem made concrete. The nodes still reachable from s through spare capacity form one side of a cut; every edge leaving that side is full, so the cut's capacity equals the flow. No flow can beat a cut, so this flow is maximum.

Implementation

Edmonds–Karp on an adjacency matrix of residual capacities, which handles reverse edges and parallel edges with no extra bookkeeping:

from collections import deque

def max_flow(n, edges, source, sink):
    res = [[0] * n for _ in range(n)]       # residual capacities
    for u, v, c in edges:
        res[u][v] += c                       # parallel edges add up
    total = 0
    while True:
        prev = [-1] * n
        prev[source] = source
        q = deque([source])
        while q and prev[sink] == -1:        # shortest augmenting path
            u = q.popleft()
            for v in range(n):
                if prev[v] == -1 and res[u][v] > 0:
                    prev[v] = u
                    q.append(v)
        if prev[sink] == -1:
            return total                     # no augmenting path left
        b, v = float('inf'), sink
        while v != source:                   # bottleneck
            b = min(b, res[prev[v]][v])
            v = prev[v]
        v = sink
        while v != source:                   # push, and open the reverse edge
            res[prev[v]][v] -= b
            res[v][prev[v]] += b
            v = prev[v]
        total += b

The same code is the reference solution to Maximum flow through a network, where it is compiled and run against the tests in all six languages.

TimeO(n · m²)
SpaceO(n²)

With a matrix, each breadth-first search is O(n²), so O(n³ · m) in this form; fine for the few hundred nodes interview problems use. Dinic's algorithm, O(n² · m), is the next step up.