Maximum Flow Computation Programming Puzzles and Competitions CIS 4900 / 5920 Spring 2009 Outline • Flow analysis • The min-cut and max-flow problems • Ford-Fulkerson and Edmonds-Karp max-flow algorithms • Start of an example problem from ICPC’07 (“Tunnels”) Flow Network • Directed graph G = (V, E) with – – – – edge capacities c(u,v) ≥ 0 a designated source node s a designated target/sink node t flows on edges f(u,v) Network a 4 2 s t 1 5 3 b c(s,a) = 2 c(s,b) = 5 c(a,b) = 1 c(a,t) = 4 c(b,t) = 3 Flow Constraints 1|2 a s f(s,a) = 1 f(a,s) = -1 f(a,b) = 1 f(b,a) = -1 f(b,t) = 1 f(t,b) = -1 4 t 1|1 5 1|3 b capacity: f(u,v) ≤ c(u,v) symmetry: f(u,v) = -f(v,u) conservation: u V {s,t} f(u,v) v V Applications • • • • • • fluid in pipes current in an electrical circuit traffic on roads data flow in a computer network money flow in an economy etc. Maximum Flow Problem Assuming – source produces the material at a steady rate – sink consumes the material at a steady rate What is the maximum net flow from s to t? Ford-Fulkerson Algorithm • Start with zero flow • Repeat until convergence: – Find an augmenting path, from s to t along which we can push more flow – Augment flow along this path Residual Capacity • Given a flow f in network G = (V, E) • Consider a pair of vertices u, v є V • Residual capacity = amount of additional flow we can push directly from u to v cf (u, v) = c(u, v) f (u, v) ≥0 since f (u, v) ≤ c(u, v) • Residual network Gf = (V, Ef ) Ef = { (u, v) є V ×V | cf (u, v) > 0 } • Example: c(u,v) = 16, f(u,v) = 5 cf (u, v) = 11 Example (1) original graph a 4 2 s t 1 5 3 b a 4 1|2 s graph with flow t 1|1 5 1|3 b Example (2) graph with flow 1|2 a s 4 t 1|1 5 1|3 b a 1 4 1 s residual graph t 1 2 5 1 b Example (3) residual graph, with flow-augmenting path a 1|1 1|4 1 s t 1 2 5 1 b 2|2 a s original graph with new flow 1|4 t 1|1 5 1|3 b Example (4) original graph with new flow 2|2 a s 1|4 t 1|1 5 1|3 2 b a 3 1 s t 1 2 new residual graph 5 b 1 Example (5) new residual graph, with augmenting path 2 a 3 1 s t 1 2 5 b 1 2|2 a s original graph with new flow 2|4 t 1 1|3 1|5 b Example (6) original graph with new flow 2|2 a s 2|4 t 1 1|3 1|5 b a 2 2 s new residual graph 2 t 1 1 1 2 4 b` Example (7) new residual graph, with augmenting path a 2 2 2 s t 1 1 1 2 4 2|2 a 2|4 b s original graph with new flow t 1 3|3 2|5 b Example (8) original graph, with new flow 2|2 a s 2|4 t 1 3|3 2|5 a 2 b 2 s residual graph (maximum flow = 5) 2 t 1 2 2 3 b Ford-Fulkerson Algorithm for (each edge (u,v) є E[G]) f[u][v] = f[v][u] = 0; while ( path p from s to t in Gf) { cf(p) = min {cf(u,v) | (u,v) є p}; for (each edge (u,v) є p) { f[u][v] = f[u][v] + cf(p) f[v][u] = -f[u][v] } } O(E) O(E) O(E x f*) f* = maximum flow, assuming integer flows, since each iteration increases flow by at least one unit int findMaxFlow (int s, int t) { int result = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) flow[i][j] = 0; for (;;) { int Increment = findAugmentingPath(s, t); if (Increment == 0) return result; result += capTo[t]; int v = t, u; while (v != s) { // augment flow along path u = prev[v]; flow[u][v] += capTo[t]; flow[v][u] -= capTo[t]; v = u; }}} static int findAugmentingPath(int s, int t) { for (int i = 0; i < n; i++) { prev[i] = -1; capTo[i] = Integer.MAX_VALUE;} int first = 0, last = 0; queue[last++] = s; prev[s] = -2; // s visited already while (first != last) { int u = queue[first++]; for (int v = 0; v < n; v++) { if (a[u][v] > 0) { int edgeCap = a[u][v] - flow[u][v]; if ((prev[v] == -1) && (edgeCap > 0)) { capTo[v] = Math.min(capTo[u], edgeCap); prev[v] = u; if (v == t) return capTo[v]; queue[last++] = v; }}}} return 0; } This uses breadth-first search, which is the basis of the Edmonds-Karp algorithm. Example: Finding Augmenting Path v4 source v1 1|3 1|1 3 3 v0 ∞ 1|3 2|4 2/3 target 2 v6 3 v3 1 1|4 v2 1 v7 1 v5 queue = { v0 } 1 = capTo = prev Application to Augmenting Path v4 2 source v1 1|3 1|1 3 3 v0 ∞ 1|3 2|4 2/3 target 2 v6 3 v3 1 1|4 v2 1 v7 2 1 v5 queue = { v1, v2 } Application to Augmenting Path v4 2 source v1 1|3 1|1 3 3 v0 ∞ 1|3 2|4 2/3 target 2 v6 3 v3 1 1|4 v2 3 v7 2 1 v5 queue = {v2} Application to Augmenting Path v4 2 source v1 1|3 1|1 3 3 v0 ∞ 1|3 v6 3 2 1 3 v7 2 1 v5 queue = {v3} target 2 v3 1|4 v2 2|4 2/3 Application to Augmenting Path 1 2 source v1 1|1 3 ∞ 3 1|3 v2 v6 3 2 1 1 v7 1 1 v5 queue = {v4, v5} target 2 v3 1|4 1 2|4 2/3 1|3 v0 v4 Application to Augmenting Path 1 2 source v1 1|1 3 3 ∞ 1|3 1 v2 v6 3 2 1 1 v7 1 1 v5 queue = { v5, v6 } 1 2 v3 1|4 Done 2|4 2/3 1|3 v0 v4 target Breadth-first search • • • • The above is an example Depth-first search is an alternative The code is nearly the same Only the queuing order differs static int findAugmentingPath(int s, int t) { for (int i = 0; i < n; i++) { prev[i] = -1; capTo[i] = Integer.MAX_VALUE;} int first = 0, last = 0; queue[last++] = s; prev[s] = -2; // s visited already while (first != last) { int u = queue[last--]; for (int v = 0; v < n; v++) { if (a[u][v] > 0) { int edgeCap = a[u][v] - flow[u][v]; if ((prev[v] == -1) && (edgeCap > 0)) { capTo[v] = Math.min(capTo[u], edgeCap); prev[v] = u; if (v == t) return capTo[v]; queue[last++] = v; }}}} return 0; } This uses depth-first search. Breadth vs. Depth-first Search • Let s be the start node ToVisit.make_empty; ToVisit.insert(s); s.marked = true; while not ToVisit.is_empty { u = ToVisit.extract; for each edge (u,v) in E if not u.marked { u.marked = true; ToVisit.insert(u); }} If Bag is a FIFO queue, we get breadth-first search; if LIFO (stack), we get dept-first. Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v0 } Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v2 } Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v2, v3 } Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v2, v3 } Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v4, v5 } Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v5 , v6} Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = {v6} Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = {v7} Breadth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = {} Depth-first Search • Now see what happens if ToVisit is implemented as a stack (LIFO). Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v0 } Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v2 } Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v3 } Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v4, v5} Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v4 } Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v6} Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1, v7} Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { v1 } Depth-first Search v4 start v1 v0 v6 v3 v2 v7 v5 ToVisit = { }