網絡流之最大流算法(EK算法)
上個博客介紹了鏈式前向星的作用,其實就是爲了Dinic算法鋪路,下面先介紹簡單一點的EK算法,在EK算法中,我們藉助BFS的方式來尋找路徑
我們就用這個地圖來講解算法的步驟,首先,我們使用二維數組volume[i][i]來記錄點i到點j的容量,flow[i][j]來記錄點i到點j的流量,我們的思路如下
- 使用bfs從起點start開始發起搜索
- 使用a[i]記錄從源點到點i的最大殘量
- 檢查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;
}
}
}
但是這個算法我在藍橋杯的試題集中測試的結果不能通過所有樣例,但是它依然是一個有效的解決方案
當數據規模增加的時候,算法本身的笨重就體現出來了