To implement Edmonds–Karp we run repeated BFS from s to t on the residual graph to find the shortest (in edges) augmenting path, augment flow along it, and repeat until no path exists.python
from collections import deque
class Edge:
def __init__(self, to, rev, cap):
self.to = to # destination node
self.rev = rev # index of reverse edge in adj[to]
self.cap = cap # remaining capacity
def edmonds_karp(n, edges, s, t):
# build adjacency list with forward and reverse edges
adj = [[] for _ in range(n)]
def add_edge(u, v, c):
adj[u].append(Edge(v, len(adj[v]), c))
adj[v].append(Edge(u, len(adj[u]) - 1, 0))
for u, v, c in edges:
add_edge(u, v, c)
flow = 0
while True:
parent = [None] * n # (node, edge_index) to reconstruct path
q = deque([s])
parent[s] = (-1, -1)
while q and parent[t] is None:
u = q.popleft()
for ei, e in enumerate(adj[u]):
if e.cap > 0 and parent[e.to] is None:
parent[e.to] = (u, ei)
q.append(e.to)
if e.to == t:
break
if parent[t] is None:
break # no more augmenting paths
# find bottleneck
v = t
bottleneck = float('inf')
while v != s:
u, ei = parent[v]
e = adj[u][ei]
bottleneck = min(bottleneck, e.cap)
v = u
# augment
v = t
while v != s:
u, ei = parent[v]
e = adj[u][ei]
e.cap -= bottleneck
adj[e.to][e.rev].cap += bottleneck
v = u
flow += bottleneck
return flow
Why O(V * E^2) worst-case:- Each BFS finds shortest path in terms of edges. Each augmentation increases the shortest-path distance (in residual graph) of at least one edge's distance to the sink after at most O(E) augmentations before that edge becomes saturated. More formally, every time an individual edge becomes saturated, its distance from s in the residual graph strictly increases; each edge can be saturated at most O(E) times across the algorithm, yielding O(E^2) augmentations, and each BFS costs O(E). Multiplying by at most O(V) different possible distances gives the well-known O(V * E^2) bound.Practical optimizations:- Use Dinic's algorithm (O(E * sqrt(V)) or O(min(V^(2/3), E^(1/2)) improvement in practice) with level graph + blocking flow for much better performance.- Use adjacency lists with Edge objects (as above) and deque for BFS (already used).- Maintain iterators/pointers per node to skip scanned edges between BFS/DFS (reduces repeated scanning).- Capacity scaling: augment using paths with large capacities first to reduce augmentation count.- Use integer types and avoid heavy object churn in hot loops (use arrays of ints in performance-critical code or C/C++).