網絡流之最大流算法(EK算法)

網絡流之最大流算法(EK算法)
上個博客介紹了鏈式前向星的作用,其實就是爲了Dinic算法鋪路,下面先介紹簡單一點的EK算法,在EK算法中,我們藉助BFS的方式來尋找路徑
在這裏插入圖片描述
我們就用這個地圖來講解算法的步驟,首先,我們使用二維數組volume[i][i]來記錄點i到點j的容量,flow[i][j]來記錄點i到點j的流量,我們的思路如下

  1. 使用bfs從起點start開始發起搜索
  2. 使用a[i]記錄從源點到點i的最大殘量
  3. 檢查a[end]的值,其代表了一條聯通路徑中的最大殘量,也就是這條路可以優化的程度,但是如果它等於0,那麼就不能繼續尋找增廣路,直接返回結果即可,否則,拿着這個優化量,更新每條路徑的流量容量,以及我們的最大流
    當然上述算法要有一個前提,就是我們要使用一個數組p[i]記錄點i的前驅,來記錄路由
    但是這樣做的結果就是,我們的確能找到一條路,但是又該怎麼認爲這條路是最優解呢,對於下面這種情況就比較尷尬
    在這裏插入圖片描述
    假如,我們按照圖中紫色路線搜索,我麼們發現最大增廣量爲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點,之後,我們就能向下走
在這裏插入圖片描述
此時,我們獲得的最大增廣量就是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;
		}
	}
}

但是這個算法我在藍橋杯的試題集中測試的結果不能通過所有樣例,但是它依然是一個有效的解決方案
在這裏插入圖片描述
當數據規模增加的時候,算法本身的笨重就體現出來了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章