Maximum Flow Computation Programming Puzzles and Competitions CIS 4900 / 5920 Spring 2009

advertisement
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 = { }
Download