基本概念
流量网络:边的权重为正的加权有向图;st-流量网络 起点s 终点t;
每条边具有属性容量以及流量,每个顶点具有流入量,流出量,净流入量=流入量-流出量;
流量配置:边的流量小于边的容量并且每个顶点是局部平衡的,净流入量为零;
最大st-流量:给定st-流量网络,寻找一种流量配置,使得s到t的流量最大;
沿着起点到终点行进时,某条边的方向与流量方向相同则为正向边,相反则为逆向边。可以通过增加正向边流量以及减小逆向边流量来增加系统总流量;
增广路径:流量增加受到路径中所有正向边未使用容量最小值和所有逆向边流量的限制,这样的路径为增广路径;
Ford-Fulkerson最大流量算法:网络中的初始流量为零,沿着任意从起点到终点的增广路径增大流量,直到网络中不存在这样的路径为止
st-切分:将顶点s和t分配与不同集合中的切分
st-切分的容量:st-边的容量之和
st-切分的跨切分流量:st-边流量之和-ts-边流量之和
最小st-切分:给定st-网络,找到容量最小的st-切分
最大流量-最小切分定理:f为st-流量网络,下面三个条件等价
1.存在某个st-切分,容量和f流量相等
2.f达到最大流量
3.f中不存在任何增广路径
剩余网络:顶点与原网络相同,原网络中每条边e:v-->w,流量fe,容量ce
如果fe>0,将w-->v加入剩余网络且容量为fe;
如果fe<ce,将v-->w加入剩余网络且容量为ce-fe;
在剩余网络中只需要增加正向边的流量便可以不断增加系统的总流量。
剩余网络中边实现
//剩余网络中的边
public class FlowEdge {
private final int v; //起点
private final int w; //终点
private final double capacity; //容量
private double flow; //流量
public FlowEdge(int v, int w, double capacity){
this.v = v;
this.w = w;
this.capacity = capacity;
this.flow = 0;
}
public int from(){ return v; }
public int to(){ return w; }
public double capacity(){ return capacity; }
public double flow(){ return flow; }
public int other(int vertex){
if(vertex==v) return w;
else return v;
}
public double residualCapacityTo(int vertex){
if(vertex==v) return flow;
else return capacity-flow;
}
public void addResidualFlowTo(int vertex, double delta){
if(vertex==v) flow -= delta;
else flow += delta;
}
public String toString(){
return String.format("%d->%d %.2f %.2f", v, w, capacity, flow);
}
}
每条都是有向边,含有属性容量以及流量,边的方向为v-->w,由剩余网络的定义,因此指向v的边的剩余流量为边的流量flow,指向w的剩余流量为容量-流量;增加流入v的流量,则w-->v边流量减小,边v-->w边流量增大。
剩余网络实现
//剩余网络
public class FlowNetwork {
private static final String NEWLINE = System.getProperty("line.separator");
private final int V;
private int E;
private Bag<FlowEdge>[] adj;
public FlowNetwork(int V){
this.V = V;
this.E = 0;
adj = new Bag[V];
for(int v=0; v<V; v++){
adj[v] = new Bag<>();
}
}
public FlowNetwork(int V, int E, int[] vr, int[] wr, double[] cr){
this(V);
for(int i=0; i<E; i++){
addEdge(new FlowEdge(vr[i], wr[i], cr[i]));
}
}
private void addEdge(FlowEdge e) {
int v = e.from();
int w = e.to();
adj[v].add(e);
adj[w].add(e);
E++;
}
public int V(){return this.V;}
public int E(){return this.E;}
public Iterable<FlowEdge> adj(int v){
return adj[v];
}
public Iterable<FlowEdge> edges(){
Bag<FlowEdge> list = new Bag<>();
for(int v=0; v<V; v++){
for(FlowEdge e : adj(v)){
list.add(e);
}
}
return list;
}
public String toString() {
StringBuilder s = new StringBuilder();
s.append(V + " " + E + NEWLINE);
for (int v = 0; v < V; v++) {
s.append(v + ": ");
for (FlowEdge e : adj[v]) {
if (e.to() != v) s.append(e + " ");
}
s.append(NEWLINE);
}
return s.toString();
}
}
类似于加权有向图的表示,使用邻接表数组来表示
最短增广路径算法
利用剩余网络,找出剩余网络中的增广路径,找到增广路径中容量的最小值,然后将该路径流量增加最小值;继续寻找是否含有增广路径,直到不含有增广路径为止;
public FordFulkerson(FlowNetwork G, int s, int t){
//如果含有增广路径
while(hasAugmentingPath(G, s, t)){
//遍历整条增广路径找到瓶颈流量
double bottle = Double.POSITIVE_INFINITY;
for(int v=t; v!=s; v = edgeTo[v].other(v)){
bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v));
}
//将该增广路径的每条路径都增加瓶颈流量
for(int v=t; v!=s; v = edgeTo[v].other(v)){
edgeTo[v].addResidualFlowTo(v, bottle);
}
value += bottle;
}
}
增广路径的求解方法
利用BFS,marked标记顶点是否搜索过,edgeTo用来存在增广路径的最后一条边,从起始点s出发进行BFS,遍历每一条边,如果发现边的剩余流量大于零且为标记,则将该边加入到edgeTo中,最后判断t是否访问过,如果访问过则说明含有增广路径,利用edgeTo可以获取增广路径
//在剩余网络中利用BFS寻找增广路径
private boolean hasAugmentingPath(FlowNetwork g, int s, int t) {
marked = new boolean[g.V()];
edgeTo = new FlowEdge[g.V()];
Queue<Integer> q = new LinkedList<>();
marked[s] = true;
q.add(s);
while(!q.isEmpty()){
int v = q.poll();
for(FlowEdge e : g.adj(v)){
int w = e.other(v);
if(e.residualCapacityTo(w)>0 && !marked[w]){
edgeTo[w] = e;
marked[w] = true;
q.add(w);
}
}
}
return marked[t];
}
总代码如下所示:
//最短增广路径最大流量算法
public class FordFulkerson {
private boolean[] marked;
private FlowEdge[] edgeTo;
private double value;
public FordFulkerson(FlowNetwork G, int s, int t){
//如果含有增广路径
while(hasAugmentingPath(G, s, t)){
//遍历整条增广路径找到瓶颈流量
double bottle = Double.POSITIVE_INFINITY;
for(int v=t; v!=s; v = edgeTo[v].other(v)){
bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v));
}
//将该增广路径的每条路径都增加瓶颈流量
for(int v=t; v!=s; v = edgeTo[v].other(v)){
edgeTo[v].addResidualFlowTo(v, bottle);
}
value += bottle;
}
}
public double value() { return value; }
public boolean inCut(int v) { return marked[v]; }
//在剩余网络中利用BFS寻找增广路径
private boolean hasAugmentingPath(FlowNetwork g, int s, int t) {
marked = new boolean[g.V()];
edgeTo = new FlowEdge[g.V()];
Queue<Integer> q = new LinkedList<>();
marked[s] = true;
q.add(s);
while(!q.isEmpty()){
int v = q.poll();
for(FlowEdge e : g.adj(v)){
int w = e.other(v);
if(e.residualCapacityTo(w)>0 && !marked[w]){
edgeTo[w] = e;
marked[w] = true;
q.add(w);
}
}
}
return marked[t];
}
public static void main(String[] args) {
int V = 6;
int E = 8;
int[] vr = new int[]{0,0,1,1,2,2,3,4};
int[] wr = new int[]{1,2,3,4,3,4,5,5};
double[] cr = new double[]{2.0,3.0,3.0,1.0,1.0,1.0,2.0,3.0};
FlowNetwork g = new FlowNetwork(V, E, vr, wr, cr);
System.out.println(g.toString());
int s = 0;
int t = g.V()-1;
FordFulkerson maxflow = new FordFulkerson(g, s, t);
System.out.println("Max flow from " + s + " to " + t);
for (int v = 0; v < g.V(); v++) {
for (FlowEdge e : g.adj(v)) {
if ((v == e.from()) && e.flow() > 0)
System.out.println(" " + e);
}
}
// print min-cut
System.out.println("Min cut: ");
for (int v = 0; v < g.V(); v++) {
if (maxflow.inCut(v)) System.out.println(v + " ");
}
System.out.println();
System.out.println("Max flow value = " + maxflow.value());
}
}
最短增广路径最大流算法求解示意图如下:
找到增广路径,增加增广路径的流量,直到不存在增广路径位置。其中增广路径为一条从起始点s到终点t的路径,该路径的每条边的剩余流量都大于零;