原創路徑:http://www.wutianqi.com/?p=3107
轉自:http://blog.csdn.net/xiaoxin_ling/article/details/19970179
Edmond Karp算法的大概思想:
反覆尋找源點s到匯點t之間的增廣路徑,若有,找出增廣路徑上每一段[容量-流量]的最小值delta,若無,則結束。
在尋找增廣路徑時,可以用BFS來找,並且更新殘留網絡的值(涉及到反向邊)。
而找到delta後,則使最大流值加上delta,更新爲當前的最大流值。
(粗體表明需要掌握的概念)
關於反向邊:
以下摘至HDOJ的課件和網上的:
首先來看一下基本的網絡流最大流模型。
有n個點,有m條有向邊,有一個點很特殊,只出不進,叫做源點,通常規定爲1號點。另一個點也很特殊,只進不出,叫做匯點,通常規定爲n號點。每條有向邊上有兩個量,容量和流量,從i到j的容量通常用c[I,j]表示,流量則通常是f[I,j]。通常可以把這些邊想象成道路,流量就是這條道路的車流量,容量就是道路可承受的最大的車流量。很顯然的,流量<=容量。而對於每個不是源點和匯點的點來說,可以類比的想象成沒有存儲功能的貨物的中轉站,所有”進入”他們的流量和等於所有從他本身”出去”的流量。
把源點比作工廠的話,問題就是求從工廠最大可以發出多少貨物,是不至於超過道路的容量限制,也就是,最大流。
比如這個圖。每條邊旁邊的數字表示它的容量。
首先,假如所有邊上的流量都沒有超過容量(不大於容量),那麼就把這一組流量,或者說,這個流,稱爲一個可行流。一個最簡單的例子就是,零流,即所有的流量都是0的流。
我們就從這個零流開始考慮,假如有這麼一條路,這條路從源點開始一直一段一段的連到了匯點,並且,這條路上的每一段都滿足流量<容量,注意,是嚴格的<,而不是<=。那麼,我們一定能找到這條路上的每一段的(容量-流量)的值當中的最小值 delta。我們把這條路上每一段的流量都加上這個delta,一定可以保證這個流依然是可行流,這是顯然的。
這樣我們就得到了一個更大的流,他的流量是之前的流量+delta,而這條路就叫做增廣路。
我們不斷地從起點開始尋找增廣路,每次都對其進行增廣,直到源點和匯點不連通,也就是找不到增廣路爲止。當找不到增廣路的時候,當前的流量就是最大流,這個結論非常重要。
尋找增廣路的時候我們可以簡單的從源點開始做bfs,並不斷修改這條路上的delta量,直到找到源點或者找不到增廣路。
這裏要先補充一點,在程序實現的時候,我們通常只是用一個c數組來記錄容量,而不記錄流量,當流量+1的時候,我們可以通過容量-1來實現,以方便程序的實現。
先來看看BFS部分的代碼(C/C++實現):
1 |
// 用BFS來判斷從結點s到t的路徑上是否還有delta |
2 |
// 即判斷s,t之間是否還有增廣路徑,若有,返回1 |
3 |
bool BFS( int s, int t) |
4 |
{ |
5 |
queue< int >
que; |
6 |
memset (pre,
-1, sizeof (pre)); |
7 |
memset (vis, false , sizeof (vis)); |
8 |
9 |
pre[s]
= s; |
10 |
vis[s]
= true ; |
11 |
que.push(s); |
12 |
13 |
int p; |
14 |
while (!que.empty()) |
15 |
{ |
16 |
p
= que.front(); |
17 |
que.pop(); |
18 |
for ( int i=1;
i<=M; ++i) |
19 |
{ |
20 |
if (r[p][i]>0
&& !vis[i]) |
21 |
{ |
22 |
pre[i]
= p; |
23 |
vis[i]
= true ; |
24 |
if (i
== t) // 存在增廣路徑 |
25 |
return true ; |
26 |
que.push(i); |
27 |
} |
28 |
} |
29 |
} |
30 |
return false ; |
31 |
} |
但事實上並沒有這麼簡單,上面所說的增廣路還不完整,比如說下面這個網絡流模型。
我們第一次找到了1-2-3-4這條增廣路,這條路上的delta值顯然是1。於是我們修改後得到了下面這個流。(圖中的數字是容量)
這時候(1,2)和(3,4)邊上的流量都等於容量了,我們再也找不到其他的增廣路了,當前的流量是1。
但這個答案明顯不是最大流,因爲我們可以同時走1-2-4和1-3-4,這樣可以得到流量爲2的流。
那麼我們剛剛的算法問題在哪裏呢?問題就在於我們沒有給程序一個”後悔”的機會,應該有一個不走(2-3-4)而改走(2-4)的機制。那麼如何解決這個問題呢?回溯搜索嗎?那麼我們的效率就上升到指數級了。
而這個算法神奇的利用了一個叫做反向邊的概念來解決這個問題。即每條邊(I,j)都有一條反向邊(j,i),反向邊也同樣有它的容量。
我們直接來看它是如何解決的:
在第一次找到增廣路之後,在把路上每一段的容量減少delta的同時,也把每一段上的反方向的容量增加delta。即在Dec(c[x,y],delta)的同時,inc(c[y,x],delta)
我們來看剛纔的例子,在找到1-2-3-4這條增廣路之後,把容量修改成如下
這時再找增廣路的時候,就會找到1-3-2-4這條可增廣量,即delta值爲1的可增廣路。將這條路增廣之後,得到了最大流2。
那麼,這麼做爲什麼會是對的呢?我來通俗的解釋一下吧。
事實上,當我們第二次的增廣路走3-2這條反向邊的時候,就相當於把2-3這條正向邊已經是用了的流量給”退”了回去,不走2-3這條路,而改走從2點出發的其他的路也就是2-4。(有人問如果這裏沒有2-4怎麼辦,這時假如沒有2-4這條路的話,最終這條增廣路也不會存在,因爲他根本不能走到匯點)同時本來在3-4上的流量由1-3-4這條路來”接管”。而最終2-3這條路正向流量1,反向流量1,等於沒有流量。
這就是這個算法的精華部分,利用反向邊,使程序有了一個後悔和改正的機會。而這個算法和我剛纔給出的代碼相比只多了一句話而已。
至此,最大流Edmond-Karp算法介紹完畢。
Edmond Karp算法具體實現(C/C++):
1 |
/** |
2 |
*
Edmond Karp |
3 |
*
Max Flow |
4 |
*
by Tanky Woo @ www.wutianqi.com |
5 |
*/ |
6 |
7 |
#include <iostream> |
8 |
#include <queue> |
9 |
#include <algorithm> |
10 |
using namespace std; |
11 |
const int msize
= 205; |
12 |
13 |
int N,
M; // N--路徑數, M--結點數 |
14 |
int r[msize][msize]; // |
15 |
int pre[msize]; //
記錄結點i的前向結點爲pre[i] |
16 |
bool vis[msize]; //
記錄結點i是否已訪問 |
17 |
18 |
// 用BFS來判斷從結點s到t的路徑上是否還有delta |
19 |
// 即判斷s,t之間是否還有增廣路徑,若有,返回1 |
20 |
bool BFS( int s, int t) |
21 |
{ |
22 |
queue< int >
que; |
23 |
memset (pre,
-1, sizeof (pre)); |
24 |
memset (vis, false , sizeof (vis)); |
25 |
26 |
pre[s]
= s; |
27 |
vis[s]
= true ; |
28 |
que.push(s); |
29 |
30 |
int p; |
31 |
while (!que.empty()) |
32 |
{ |
33 |
p
= que.front(); |
34 |
que.pop(); |
35 |
for ( int i=1;
i<=M; ++i) |
36 |
{ |
37 |
if (r[p][i]>0
&& !vis[i]) |
38 |
{ |
39 |
pre[i]
= p; |
40 |
vis[i]
= true ; |
41 |
if (i
== t) // 存在增廣路徑 |
42 |
return true ; |
43 |
que.push(i); |
44 |
} |
45 |
} |
46 |
} |
47 |
return false ; |
48 |
} |
49 |
50 |
int EK( int s, int t) |
51 |
{ |
52 |
int maxflow
= 0, d; |
53 |
while (BFS(s,
t)) |
54 |
{ |
55 |
d=
INT_MAX; |
56 |
//
若有增廣路徑,則找出最小的delta |
57 |
for ( int i=t;
i!=s; i=pre[i]) |
58 |
d
= min(d, r[pre[i]][i]); |
59 |
//
這裏是反向邊,看講解 |
60 |
for ( int i=t;
i!=s; i=pre[i]) |
61 |
{ |
62 |
r[pre[i]][i]
-= d; |
63 |
r[i][pre[i]]
+= d; |
64 |
} |
65 |
maxflow
+= d; |
66 |
} |
67 |
return maxflow; |
68 |
} |
69 |
70 |
int main() |
71 |
{ |
72 |
while (cin
>> N >> M) |
73 |
{ |
74 |
memset (r,
0, sizeof (r)); |
75 |
int s,
e, c; |
76 |
for ( int i=0;
i<N; ++i) |
77 |
{ |
78 |
cin
>> s >> e >> c; |
79 |
r[s][e]
+= c; // 有重邊時則加上c |
80 |
} |
81 |
82 |
cout
<< EK(1, M) << endl; |
83 |
} |
84 |
return 0; |
85 |
} |