网络流之最大流算法(EK算法)

网络流之最大流算法(EK算法)
上个博客介绍了链式前向星的作用,其实就是为了Dinic算法铺路,下面先介绍简单一点的EK算法,在EK算法中,我们借助BFS的方式来寻找路径
网络流之最大流算法(EK算法)_第1张图片
我们就用这个地图来讲解算法的步骤,首先,我们使用二维数组volume[i][i]来记录点i到点j的容量,flow[i][j]来记录点i到点j的流量,我们的思路如下

  1. 使用bfs从起点start开始发起搜索
  2. 使用a[i]记录从源点到点i的最大残量
  3. 检查a[end]的值,其代表了一条联通路径中的最大残量,也就是这条路可以优化的程度,但是如果它等于0,那么就不能继续寻找增广路,直接返回结果即可,否则,拿着这个优化量,更新每条路径的流量容量,以及我们的最大流
    当然上述算法要有一个前提,就是我们要使用一个数组p[i]记录点i的前驱,来记录路由
    但是这样做的结果就是,我们的确能找到一条路,但是又该怎么认为这条路是最优解呢,对于下面这种情况就比较尴尬
    网络流之最大流算法(EK算法)_第2张图片
    假如,我们按照图中紫色路线搜索,我么们发现最大增广量为1,然后我们使用1改变流量和容量以后,在下次增广是发现最大增广量就是0了,因为B-C的已经不能在承受更多的流量,但是显然,如果我们使用A-E-F-D的路线,我们可以获得一个为2的最大流,也就是说,我们的程序需要一个反悔的机会,这就是最大流中的反相弧的应用
    反相弧
    反相弧的存在目的在于让程序能够反悔上一步操作,在更新流量的时候,我们也对反向弧进行更新
//p[i]表示i的前驱节点
for(int i = n; i > start; i=p[i]) {
				flow[p[i]][i] = flow[p[i]][i] + a[end];
				flow[i][p[i]] = flow[i][p[i]] - a[end];
			}

那这个反相弧怎么生效呢,就像刚才情况,我们继续寻找最大增广量,我们来到B点以后,我们拥有了另一个可能,那就是往回走,到A点,之后,我们就能向下走
网络流之最大流算法(EK算法)_第3张图片
此时,我们获得的最大增广量就是2,这个2被加到原先的1上面得到的结果就是3,我们也就得到了最大流
下面是实现的java代码

import java.util.LinkedList;
import java.util.Scanner;

//增广路算法
public class Karp {

	private static int[][] flow;  //flow[i][j]描述i到j的流量
	private static int[][] volume; //volume[i][j]描述i到j的最大容量
	private static int[] a; //a[i]表示源点到i路径上的最小残量
	private static int[] p; //p[i]表示i点的前驱节点
	private static int start, end, sum; //定义源点和汇点和最大流量
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		flow = new int[n+1][n+1];
		volume = new int[n+1][n+1];
		a = new int[n+1];
		p = new int[n+1];
		start = 1;
		end = n;
		sum = 0;
		for(int i = 0; i < m; i++) {
			int x = sc.nextInt();
			int y = sc.nextInt();
			int z = sc.nextInt();
			volume[x][y] = volume[x][y] + z;
		}
		Node_Karp(n, m);
	}
	
	public static void Node_Karp(int n, int m) {
		//此处要使用bfs的方式寻找增广路
		LinkedList<Integer> queue = new LinkedList<Integer>();
		while(true) {
			//每次都要更新残量
			inita();
			a[start] = Integer.MAX_VALUE;
			queue.addLast(start);
			while(queue.size()!=0) {
				int now = queue.removeFirst();
				for(int i = 1; i <= n; i++) {
					if((a[i]==0)&&(flow[now][i]<volume[now][i])) {
						p[i] = now;
						queue.addLast(i);
						if(a[now] >= (volume[now][i]-flow[now][i])) {
							//这里更新了残留量
							a[i] = volume[now][i]-flow[now][i];
						}
						else {
							a[i] = a[now];
						}
					}
				}
			}
			if(a[end]==0) {
				//已经找不到增广路,说明已经是最大流
				break;
			}
			sum = sum + a[end];
			for(int i = n; i > start; i=p[i]) {
				flow[p[i]][i] = flow[p[i]][i] + a[end];
				flow[i][p[i]] = flow[i][p[i]] - a[end];
			}
		}
		System.out.println(sum);
	}
	
	
	public static void inita() {
		for(int i = 0; i <= end; i++) {
			a[i] = 0;
		}
	}
}

但是这个算法我在蓝桥杯的试题集中测试的结果不能通过所有样例,但是它依然是一个有效的解决方案
网络流之最大流算法(EK算法)_第4张图片
当数据规模增加的时候,算法本身的笨重就体现出来了

你可能感兴趣的:(数据结构)