bhylu.com

Advertisements

DSA Edmonds-Karp Algorithm

Edmonds–Karp is a Ford–Fulkerson method that chooses a shortest augmenting path by number of residual edges using BFS. The capacity amount is still determined by the path bottleneck.

How it works

The same residual-update implementation can use a queue instead of a stack. That choice gives the algorithm a polynomial bound independent of the numeric maximum-flow value under the usual graph model. After no augmenting path remains, vertices reachable from the source through positive residual edges define one side of a minimum cut. The cut’s original crossing capacity must equal the returned maximum-flow value.

Run the code

Select Try in playground to load this complete source into the existing Python editor. It does not run automatically; choose Run when ready. These examples use only the Python standard library and small fictional inputs. They do not need database services, extra packages or personal files.

For local work, save the complete source as edmonds-karp.py and run python edmonds-karp.py. The local review uses Python 3.12.14; the browser uses the existing Pyodide 0.27.7 runtime. Keep inputs small, respect explicit table/tour budgets and account for recursion limits before increasing a fixture’s size.

Example

edmonds-karp.py

from collections import deque

def max_flow(capacity, source, sink, strategy="bfs"):
    if source not in capacity or sink not in capacity or source == sink:
        raise ValueError("Distinct declared source and sink required")
    if strategy not in ("bfs", "dfs"):
        raise ValueError("Unknown path strategy")
    residual = {vertex: {} for vertex in capacity}
    for vertex, neighbors in capacity.items():
        for target, amount in neighbors.items():
            if target not in capacity or type(amount) is not int or amount < 0:
                raise ValueError("Nonnegative integer capacities required")
            residual[vertex][target] = residual[vertex].get(target, 0) + amount
            residual[target].setdefault(vertex, 0)
    value = 0
    while True:
        parent = {source: None}
        pending = deque([source])
        while pending and sink not in parent:
            vertex = pending.popleft() if strategy == "bfs" else pending.pop()
            for target, available in residual[vertex].items():
                if available > 0 and target not in parent:
                    parent[target] = vertex
                    pending.append(target)
                    if target == sink:
                        break
        if sink not in parent:
            return value, residual
        amount = None
        target = sink
        while target != source:
            previous = parent[target]
            available = residual[previous][target]
            amount = available if amount is None else min(amount, available)
            target = previous
        target = sink
        while target != source:
            previous = parent[target]
            residual[previous][target] -= amount
            residual[target][previous] += amount
            target = previous
        value += amount

capacity = {"tank": {"oak": 3, "pond": 2}, "oak": {"pond": 1, "beds": 2}, "pond": {"beds": 3}, "beds": {}}
value, residual = max_flow(capacity, "tank", "beds", strategy="bfs")
reached, pending = {"tank"}, ["tank"]
while pending:
    vertex = pending.pop()
    for target, amount in residual[vertex].items():
        if amount > 0 and target not in reached:
            reached.add(target)
            pending.append(target)
cut = sum(amount for vertex in reached for target, amount in capacity[vertex].items() if target not in reached)
print(value, cut)
print("beds" in reached)

Expected result

5 5
False

The sink is not residual-reachable and the cut capacity equals five. This equality is stronger evidence than seeing a plausible total alone. The exhaustive boundary review enumerates source/sink cuts on small networks and compares their minimum with both path strategies.

Time, space and assumptions

The standard bound is O(VE²) with adjacency-style residual searches and O(V+E) stored residual state. Arithmetic on unbounded-size capacities can add bit-complexity costs beyond that unit-cost model.

Consult the Python 3.12 standard-library reference for container and language contracts; an example result is not a proof of every possible input or real-world performance.

Advertisements