EK算法(网络流,最大流)
一、網(wǎng)絡與網(wǎng)絡流
給一個有向圖(V,E),在V中指定一點,稱為源點(記為vs),和另一點,稱為匯點(記為vt),其余的點叫做中間點。對于E中每條弧(vi,vj)都對應一個正整數(shù)c(vi,vj)>=0(或簡寫為cij),稱為f的容量,則賦權(quán)有向圖N=(V,E,c,vs,vt)稱為一個網(wǎng)絡。所謂網(wǎng)絡上的流,是指定義在弧集E上的一個函數(shù)f=f{f{vi,vj}},并稱f(vi,vj)為弧(vi,vj)上的流量(下面簡記為fij)。因此弧上有兩個數(shù),第一個表示容量cij,第二個表示流量fij。
二、可行流與最大流
在運輸網(wǎng)絡的實際問題中,我們可以看出,對于流有兩個顯然的要求:一是每個弧上的流量不能超過該弧的容量;二是中間點的流量為0,源點的凈流出量和匯點的凈流入量必相等且為這個方案的總輸送量。因此有:
(1)容量約束:0≤fij≤cij,(vi,vj)∈E,
(2)守恒條件
對于中間點:流入量=流出量;對于源點與匯點:源點的凈流出量vs(f)=匯點的凈流入量(-vt(f))的流f,稱為網(wǎng)絡N上的可行流,并將源點s的凈流量稱為流f的流值v(f)。
網(wǎng)絡N中流值最大的流f*稱為N的最大流。?
三、可增廣路徑
所為可增廣路徑,是指這條路徑上的流可以修改,通過修改,使得整個網(wǎng)絡的流值增大。
設f是一個可行流,P是從源點s到匯點t的一條路徑,若P滿足下列條件:
(1)在p上的所有前向弧(vi→vj)都是非飽和弧,即0≤fij<cij
(2)在p上的所有后向弧(vi←vj)都是非零弧,即0<fij≤cij
則稱p為(關于可行流f的)一條可增廣路徑。
四、最大流定理
當且僅當不存在關于f*的增廣路徑,可行流f*為最大流。
?
五、最大流算法
算法思想:最大流問題實際上是求一可行流{fij},使得v(f達到最大。若給了一個可行流f,只要判斷N中有無關于f的增廣路徑,如果有增廣路徑,改進f, 得到一個流量增大的新的可行流;如果沒有增廣路徑,則得到最大流。
1.尋求最大流的標號法(Ford,Fulkerson)
?從一個可行流(一般取零流)開始,不斷進行以下的標號過程與調(diào)整過程,直到找不到關于f的可增廣路徑為止。
??? (1)標號過程
??? 在這個過程中,網(wǎng)絡中的點分為已標號點和未標號點,已標號點又分為已檢查和未檢查兩種。每個標號點的標號信息表示兩個部分:第一標號表明它的標號從哪一點得到的,以便從vt開始反向追蹤找出也增廣路徑;第二標號是為了表示該頂點是否已檢查過。
??? 標號開始時,給vs標上(s,0),這時vs是標號但末檢查的點,其余都是未標號的點,記為(0,0)。
??? 取一個標號而未檢查的點vi,對于一切未標號的點vj:
??? A.對于弧(vi,vj),若fij<cij,則給vj標號(vi,0),這時,vj點成為標號而未檢查的點。
??? B.對于弧(vi,vj),若fji>0,則給vj標號(-vi,0),這時,vj點成為標號而未檢查的點。
??? 于是vi成為標號且已檢查的點,將它的第二個標號記為1。重復上述步驟,一旦vt被標上號,表明得到一條從vi到vt的增廣路徑p,轉(zhuǎn)入調(diào)整過程。
??? 若所有標號都已檢查過去,而標號過程進行不下去時,則算法結(jié)束,這時的可行流就是最大流。
? (2)調(diào)整過程
??? 從vt點開始,通過每個點的第一個標號,反向追蹤,可找出增廣路徑P。例如設vt的第一標號為vk(或-vk),則弧(vk,vt)(或 相應地(vt,vk))是p上弧。接下來檢查vk的第一標號,若為vi(或-vi),則找到(vi,vk)(或相應地(vk,vi))。再檢查vi的第一 標號,依此類推,直到vs為止。這時整個增廣路徑就找到了。在上述找增廣路徑的同時計算Q:
????????? Q=min{min(cij-fij),minf*ij}
??? 對流f進行如下的修改:
??? f'ij =? fij+Q?? (vi,vj)∈ P的前向弧的集合
??? f'ij =? fij-Q?? (vi,vj)∈ P的后向弧的集合
??? f'ij =? f*ij???? (vi,vj)不屬于P的集合
接著,清除所有標號,對新的可行流f’,重新進入標號過程。
?
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面說一下EK算法
1、首先,什么是最大流?
最大流就是從源點到經(jīng)過的所有路徑的最終到達匯點的所有流量和
2、EK算法的核心是反復尋找源點S到匯點t之間的增廣路徑,若有,找出增廣路徑上每一段[容量-流量]的最小值delta,若無,則結(jié)束。在尋找增廣路徑時,可以用BFS來找,并且更新殘留網(wǎng)絡的值(涉及到反向邊)。
而找到delta后,則使最大流加上delta,更新為當前的最大流值。
這么一個圖,求源點1,到匯點4的最大流
*** 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,則flow[3]=min(flow[2]=40,30)=30;
capacity[2][4]=20,但是pre[4]=1(已經(jīng)在capacity[1][4]這遍歷過4號點了)
capacity[3][4]......
當index=4(匯點),結(jié)束增廣路的尋找
傳遞回increasement(該路徑的流),利用pre前驅(qū)尋找路徑
圖也被改成
接下來同理
這就是最終完成的圖,最終sumflow=20+20+10=50(這個就是最大流的值)
PS,為什么要有反向邊呢??
我們第一次找到了1-2-3-4這條增廣路,這條路上的delta值顯然是1。于是我們修改后得到了下面這個流。(圖中的數(shù)字是容量)
這時候(1,2)和(3,4)邊上的流量都等于容量了,我們再也找不到其他的增廣路了,當前的流量是1。
但這個答案明顯不是最大流,因為我們可以同時走1-2-4和1-3-4,這樣可以得到流量為2的流。
那么我們剛剛的算法問題在哪里呢?問題就在于我們沒有給程序一個”后悔”的機會,應該有一個不走(2-3-4)而改走(2-4)的機制。那么如何解決這個問題呢?回溯搜索嗎?那么我們的效率就上升到指數(shù)級了。
而這個算法神奇的利用了一個叫做反向邊的概念來解決這個問題。即每條邊(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這條正向邊已經(jīng)是用了的流量給”退”了回去,不走2-3這條路,而改走從2點出發(fā)的其他的路也就是2-4。(有人問如果這里沒有2-4怎么辦,這時假如沒有2-4這條路的話,最終這條增廣路也不會存在,因為他根本不能走到匯點)同時本來在3-4上的流量由1-3-4這條路來”接管”。而最終2-3這條路正向流量1,反向流量1,等于沒有流量。
這就是這個算法的精華部分,利用反向邊,使程序有了一個后悔和改正的機會。而這個算法和我剛才給出的代碼相比只多了一句話而已。
至此,最大流Edmond-Karp算法介紹完畢。
?
?六、EK算法模板
#include<bits/stdc++.h> using namespace std; #define arraysize 201 int maxData=0x7fffffff; int capacity[arraysize][arraysize];//記錄殘留網(wǎng)絡的容量 int flow[arraysize];//標記從源點到當前節(jié)點實際還剩多少流量可用 int pre[arraysize];//標記在這條路徑上當前節(jié)點的前驅(qū),同時標記該節(jié)點是否在隊列中 int n,m; queue<int>myqueue; int BFS(int src,int des) {int i,j;while(!myqueue.empty())//隊列清空myqueue.pop();for(i=1;i<m+1;++i){pre[i]=-1;}pre[src]=0;flow[src]=maxData;myqueue.push(src);while(!myqueue.empty()){int index=myqueue.front();myqueue.pop();if(index==des)//找到了增廣路徑break;for(i=1;i<m+1;++i){if(i!=src&&capacity[index][i]>0&&pre[i]==-1){pre[i]=index;//記錄前驅(qū)flow[i]=min(capacity[index][i],flow[index]);//關鍵:迭代的找增量myqueue.push(i);}}}if(pre[des]==-1)//殘留途中不再存在增廣路徑return -1;elsereturn flow[des]; } int maxFlow(int src,int des) {int increasement= 0;int sumflow = 0;while((increasement=BFS(src,des))!=-1){int k = des; //利用前驅(qū)尋找路徑while(k!=src){int last = pre[k];capacity[last][k] -= increasement; //改變正向邊的容量capacity[k][last] += increasement; //改變反向邊的容量k = last;}sumflow += increasement;}return sumflow; } int main() {int i,j;int start,end,ci;while(cin>>n>>m)//邊的個數(shù)和終點{memset(capacity,0,sizeof(capacity));memset(flow,0,sizeof(flow));for(i=0;i<n;++i){cin>>start>>end>>ci;if(start == end) //考慮起點終點相同的情況continue;capacity[start][end] +=ci; //此處注意可能出現(xiàn)多條同一起點終點的情況}cout<<maxFlow(1,m)<<endl;}return 0; }最大流 EK算法
總結(jié)
以上是生活随笔為你收集整理的EK算法(网络流,最大流)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jquery post php返回htm
- 下一篇: EDIUS 3.6快捷键