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
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.