網絡流看了兩天,終於有了一點眉目,也拿模版A了道題目,通過對於模版代碼的調試也真正了解了ek算法的用途了。
想好好寫下總結都不讓人順心,寫到一半網站死了,又得重新寫。。
不說廢話了,直接正題
首先要先清楚最大流的含義,就是說從源點到經過的所有路徑的最終到達匯點的所有流量和
EK算法的核心
反復尋找源點s到匯點t之間的增廣路徑,若有,找出增廣路徑上每一段[容量-流量]的最小值delta,若無,則結束。
在尋找增廣路徑時,可以用BFS來找,並且更新殘留網絡的值(涉及到反向邊)。
而找到delta后,則使最大流值加上delta,更新為當前的最大流值。

這么一個圖,求源點1,到匯點4的最大流
由於我是通過模版真正理解ek的含義,所以先上代碼,通過分析代碼,來詳細敘述ek算法
1 #include <iostream> 2 #include <queue> 3 #include<string.h> 4 using namespace std; 5 #define arraysize 201 6 int maxData = 0x7fffffff; 7 int capacity[arraysize][arraysize]; //記錄殘留網絡的容量 8 int flow[arraysize]; //標記從源點到當前節點實際還剩多少流量可用 9 int pre[arraysize]; //標記在這條路徑上當前節點的前驅,同時標記該節點是否在隊列中 10 int n,m; 11 queue<int> myqueue; 12 int BFS(int src,int des) 13 { 14 int i,j; 15 while(!myqueue.empty()) //隊列清空 16 myqueue.pop(); 17 for(i=1;i<m+1;++i) 18 { 19 pre[i]=-1; 20 } 21 pre[src]=0; 22 flow[src]= maxData; 23 myqueue.push(src); 24 while(!myqueue.empty()) 25 { 26 int index = myqueue.front(); 27 myqueue.pop(); 28 if(index == des) //找到了增廣路徑 29 break; 30 for(i=1;i<m+1;++i) 31 { 32 if(i!=src && capacity[index][i]>0 && pre[i]==-1) 33 { 34 pre[i] = index; //記錄前驅 35 flow[i] = min(capacity[index][i],flow[index]); //關鍵:迭代的找到增量 36 myqueue.push(i); 37 } 38 } 39 } 40 if(pre[des]==-1) //殘留圖中不再存在增廣路徑 41 return -1; 42 else 43 return flow[des]; 44 } 45 int maxFlow(int src,int des) 46 { 47 int increasement= 0; 48 int sumflow = 0; 49 while((increasement=BFS(src,des))!=-1) 50 { 51 int k = des; //利用前驅尋找路徑 52 while(k!=src) 53 { 54 int last = pre[k]; 55 capacity[last][k] -= increasement; //改變正向邊的容量 56 capacity[k][last] += increasement; //改變反向邊的容量 57 k = last; 58 } 59 sumflow += increasement; 60 } 61 return sumflow; 62 } 63 int main() 64 { 65 int i,j; 66 int start,end,ci; 67 while(cin>>n>>m) 68 { 69 memset(capacity,0,sizeof(capacity)); 70 memset(flow,0,sizeof(flow)); 71 for(i=0;i<n;++i) 72 { 73 cin>>start>>end>>ci; 74 if(start == end) //考慮起點終點相同的情況 75 continue; 76 capacity[start][end] +=ci; //此處注意可能出現多條同一起點終點的情況 77 } 78 cout<<maxFlow(1,m)<<endl; 79 } 80 return 0; 81 }
顯而易見capacity存變的流量,進行ek求解
對於BFS找增廣路:
1. flow[1]=INF,pre[1]=0;
源點1進隊列,開始找增廣路,capacity[1][2]=40>0,則flow[2]=min(flow[1],40)=40;
capacity[1][4]=20>0,則flow[4]=min(flow[1],20)=20;
capacity[2][3]=30>0,則flow[3]=min(folw[2]=40,30)=30;
capacity[2][4]=30,但是pre[4]=1(已經在capacity[1][4]這遍歷過4號點了)
capacity[3][4].....
當index=4(匯點),結束增廣路的尋找
傳遞回increasement(該路徑的流),利用前驅pre尋找路徑
路徑也自然變成了這樣:

2.flow[1]=INF,pre[1]=0;
源點1進隊列,開始找增廣路,capacity[1][2]=40>0,則flow[2]=min(flow[1],40)=40;
capacity[1][4]=0!>0,跳過
capacity[2][3]=30>0,則flow[3]=min(folw[2]=40,30)=30;
capacity[2][4]=30,pre[4]=2,則flow[2][4]=min(flow[2]=40,20)=20;
capacity[3][4].....
當index=4(匯點),結束增廣路的尋找
傳遞回increasement(該路徑的流),利用前驅pre尋找路徑
圖也被改成

接下來同理

這就是最終完成的圖,最終sumflow=20+20+10=50(這個就是最大流的值)
PS,為什么要有反向邊呢?

我們第一次找到了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算法介紹完畢。
