網絡流之最大流的增廣路徑算法


擴展:多路增廣

一般的,在執行增廣路算法時,都是先用BFS或DFS從源到匯找到一條增廣路,記錄下應修改的流量,然后再順着路倒回去增廣.反復這個過程直到增廣路找不到了為止.
 
顯然的,我們做了很多無用功,假設有兩條很長的增廣路,前面大部分都是重疊的,只是在最后關頭分了個岔,而程序卻把前面很長的路走了兩次.
 
為什么要這樣?
 
不妨把兩條增廣路合並起來,不止是兩條,所有的增廣路都可以按其前綴合並起來,而形成一棵增廣樹.找增廣樹可以用DFS,正如生成搜索樹一樣.
最大流增廣路算法的擴展,多路增廣
 
簡而言之,對於當前的每一個結點記錄一個可提供的最大流量,源點的可供流量顯然是無窮大,當推到下一個點時,最大流量取邊的容量和上個點提供的最大流量的較小值.
 
當到達匯點時,自然DFS開始回朔,這時按當前點的已用流量增廣當前點與他父親相連的邊,同時將增廣值累加到他父親的已用流量上,並在他父親的可提供流量上減掉這個值,以便在搜索他父親剩下的兒子時,讓所有兒子的已用流量總和不大於父親的可提供流量,不然就出錯了.
 
我們按照以下步驟做:
search node(available)
{
  1.得到node的可提供流量available
  2.search node's all son(可提供流量)
    {
        inc(已用流量,當前兒子實際增廣的流量)
        dec(可提供流量,<同上>)
    }
  3.用得到的已用流量的值增廣邊(node-node's father)
  4.return 邊的增廣量 到 node'fahter
}
 
每進行一次以上步驟,我們就完成了一次多路增廣,並且返回了一個值到源點,即DFS的根.這個值表示本次操作中將流量擴大了多少.重復操作直到返回值為0.得到最大流的數值即累加每次的返回值.
 

SAP(最短增廣路算法) 最大流模板

#include <iostream>
#include <queue>
#define msize 1024      //最大頂點數目
using namespace std;
 
int d[msize];           //標號
int r[msize][msize];    //殘留網絡,初始為原圖
int num[msize];         //num[i]表示標號為i的頂點數有多少
int pre[msize];
int n,m,s,t;            //m個頂點,n條邊,從源點s到匯點t
 
void ini_d() //BFS計算標號,匯點t標號為0
{
    int k;
    queue<int>Q;
 
    memset(d,1,sizeof(d));
    memset(num,0,sizeof(num));
 
    Q.push(t);
    d[t]=0;
    num[0]=1;
    while (!Q.empty())
    {
        k=Q.front(),Q.pop();
        for (int i=0;i<m;i++)
        {
            if (d[i]>=m&&r[i][k]>0)
            {
                d[i]=d[k]+1;
                Q.push(i);
                num[d[i]]++;
            }
        }
    }
}
 
int findAlowArc(int i)       //從i出發尋找允許弧
{
    int j;
    for (j=0;j<m;j++)

        if (r[i][j]>0&&d[i]==d[j]+1) return j;
 
    return -1;
}
 
int reLable(int i)         //重新標號
{
    int mm=INT_MAX;
    for (int j=0;j<m;j++)
        if (r[i][j]>0) mm=min(mm,d[j]+1);
 
    return mm==INT_MAX?m:mm;
}
 
int maxFlow(int s,int t)      //從源點s出發的最大流
{
    int flow=0,i=s,j;
    int delta;              //增量
 
    memset(pre,-1,sizeof(pre));
    while (d[s]<m)
    {
        j=findAlowArc(i);
        if (j>=0)
        {
            pre[j]=i;
            i=j;
            if (i==t)           //更新殘留網絡
            {
                delta=INT_MAX;
                for (i=t;i!=s;i=pre[i])

                     delta=min(delta,r[pre[i]][i]);
                for (i=t;i!=s;i=pre[i])

                     r[pre[i]][i] -= delta, r[i][pre[i]] += delta;
                flow += delta;
            }
        }
        else
        {
            int x=reLable(i);       //重新標號
            num[x]++;
            num[d[i]];
            if (num[d[i]]==0) return flow;      //間隙優化
            d[i]=x;
            if (i!=s) i=pre[i];
        }
    }
 
    return flow;

 

FF算法

分為三個步驟,首先尋找一條增廣路(如果沒有增廣路,則返回最大流),同時保存路徑;其次,對路徑上的流量求最小值,記為min;最后根據路徑修改網絡,對於前向邊,減去最小值,對於后向邊,加上最小值,或者直接修改容量。

增廣路:從源點s到匯點t的一條有向路徑,如果從源點開始不可以到匯點,那么沒有增廣路。

保存:用一個數組保存,一般是采用父親表示法(父鏈接),保存當前節點的父親, 尋找的時候采用的是迭代的方式實現求最小值以及修改原網絡。

尋找增廣路,采用bfs的方式。詳細算法如下:

#include <iostream>
#include <queue>
using namespace std;
const int N = 210;
const int INF = 0x7FFFFFFF;
int n,m,map[N][N],path[N],flow[N],start,end;
queue<int> q;

int bfs()

{
     int i,t;
     while(!q.empty()) 

             q.pop();   //清空隊列
     memset(path,-1,sizeof(path));  //保存父親的數組,初始化為-1
     path[start]=0,flow[start]=INF;   //flow[] 保存當前的最小容量
     q.push(start);
     while(!q.empty())

     {
         t=q.front();
         q.pop();
         if(t==end)   break;   //找到一條增廣路
         for(i=1;i<=m;i++)

         {
             if(i!=start && path[i]==-1 && map[t][i])

                {
                             flow[i]=flow[t]<map[t][i]?flow[t]:map[t][i];
                            q.push(i);
                            path[i]=t;
                }
         }
     }
     if(path[end]==-1return -1;   //如果匯點的父親等於-1,說明不能到達最后。
     return flow[m];                   //一次遍歷之后的流量增量,min
 }
 int Edmonds_Karp()

{
     int max_flow=0,step,now,pre;
     while((step=bfs())!=-1)

     {          //找不到增路徑時退出
               max_flow+=step;
               now=end;
              while(now!=start)

             {
                        pre=path[now];
                        map[pre][now]-=step;      //更新正向邊的實際容量
                       map[now][pre]+=step;      //添加反向邊
                       now=pre;
              }
     }
     return max_flow;
 }
 int main()

{
     int i,u,v,cost;
     while(scanf("%d %d",&n,&m)!=EOF)

     {
               memset(map,0,sizeof(map));
              for(i=0;i<n;i++)

              {
                      scanf("%d %d %d",&u,&v,&cost);
                      map[u][v]+=cost;           //not just only one input
              }
              start=1,end=m;
              printf("%d\n",Edmonds_Karp());
      }
     return 0;
 }

主要分為模塊三個:

模塊1:

int FF()

{

     while(有增廣路){

          更新原圖的前向邊(減min),后向邊(+min),同時保存最大流的增量值。

         }

        返回最大流值。

}

模塊2:

int agument_path()

{

     源點進隊;

    while(隊列不空)

    {  出隊;

      如果達到匯點,則結束;

      for(i=1;i<=m;i++){ 

      //搜索整個圖的鄰接邊

     如果未保存,且邊不為0(可增的量),且不是開始節點

             進隊,保存,求最小值

    }

    如果匯點的父親沒有,說明沒有增廣路,返回-1;

   返回最小值;

}

 

模塊3:

building{

for(i=0;i<n;i++){
             scanf("%d %d %d",&u,&v,&cost);
             map[u][v]+=cost;           //not just only one input

}
最大流的建圖思想:對於每條邊,直接累加其容量就可以了。

 


免責聲明!

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



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