最大网络流Ford-Fulkerson算法

基本概念

流量网络:边的权重为正的加权有向图;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的路径,该路径的每条边的剩余流量都大于零;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ford-Fulkerson算法是求解最大问题的一种经典算法。以下是一个基于增广路思想的Ford-Fulkerson算法的代码: ``` // 基于邻接矩阵的Ford-Fulkerson算法实现 #include <iostream> #include <queue> #include <cstring> using namespace std; const int MAXN = 100; // 最大顶点数 const int INF = 0x3f3f3f3f; // 表示无穷大 int n, m; // n表示顶点数,m表示边数 int s, t; // s表示源点,t表示汇点 int cap[MAXN][MAXN]; // 表示容量 int flow[MAXN][MAXN]; // 表示量 int pre[MAXN]; // 表示前驱节点 int bfs() { memset(pre, -1, sizeof(pre)); // 初始化前驱节点数组 queue<int> q; q.push(s); pre[s] = -2; while (!q.empty()) { int u = q.front(); q.pop(); for (int v = 0; v < n; ++v) { if (pre[v] == -1 && cap[u][v] > flow[u][v]) { pre[v] = u; if (v == t) return 1; q.push(v); } } } return 0; } int maxFlow() { int ans = 0; while (bfs()) { int minflow = INF; for (int u = t; u != s; u = pre[u]) { int v = pre[u]; minflow = min(minflow, cap[v][u] - flow[v][u]); } for (int u = t; u != s; u = pre[u]) { int v = pre[u]; flow[v][u] += minflow; flow[u][v] -= minflow; } ans += minflow; } return ans; } int main() { cin >> n >> m >> s >> t; memset(cap, 0, sizeof(cap)); memset(flow, 0, sizeof(flow)); for (int i = 0; i < m; ++i) { int u, v, c; cin >> u >> v >> c; cap[u][v] += c; // 注意有可能存在重边 } cout << maxFlow() << endl; return 0; } ``` 算法思路: 1. 初始化量为0; 2. 在剩余容量大于0的情况下,寻找增广路: - 从源点s开始,使用BFS寻找一条增广路; - 如果找到增广路,计算增广路上的最小剩余容量minflow,更新量; 3. 最大就是所有增广路上的最小剩余容量之和。 其中,增广路的定义是指从源点到汇点路径上,剩余容量均大于0的路径。在Ford-Fulkerson算法中,每次都需要寻找一条增广路来更新量,直到无法再找到增广路为止。这个过程中,每次找到的增广路都可以使得量增加,因此最终的量是不断增加的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值