網絡流—最大流(Edmond-Karp算法)


 網絡流看了兩天,終於有了一點眉目,也拿模版A了道題目,通過對於模版代碼的調試也真正了解了ek算法的用途了。

 想好好寫下總結都不讓人順心,寫到一半網站死了,又得重新寫。。
 

不說廢話了,直接正題

首先要先清楚最大流的含義,就是說從源點到經過的所有路徑的最終到達匯點的所有流量和


EK算法的核心
反復尋找源點s到匯點t之間的增廣路徑,若有,找出增廣路徑上每一段[容量-流量]的最小值delta,若無,則結束。
在尋找增廣路徑時,可以用BFS來找,並且更新殘留網絡的值(涉及到反向邊)。
而找到delta后,則使最大流值加上delta,更新為當前的最大流值。

這么一個圖,求源點1,到匯點4的最大流

由於我是通過模版真正理解ek的含義,所以先上代碼,通過分析代碼,來詳細敘述ek算法

View Code
 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算法介紹完畢。

 

部分轉載於:http://www.wutianqi.com/?p=3107


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM