扩展:多路增广

一般的,在执行增广路算法时,都是先用BFS或DFS从源到汇找到一条增广路,记录下应修改的流量,然后再顺着路倒回去增广.反复这个过程直到增广路找不到了为止.

显然的,我们做了很多无用功,假设有两条很长的增广路,前面大部分都是重叠的,只是在最后关头分了个岔,而程序却把前面很长的路走了两次.

为什么要这样?

不妨把两条增广路合并起来,不止是两条,所有的增广路都可以按其前缀合并起来,而形成一棵增广树.找增广树可以用DFS,正如生成搜索树一样.

增广路算法 python 增广路径_最小值


简而言之,对于当前的每一个结点记录一个可提供的最大流量,源点的可供流量显然是无穷大,当推到下一个点时,最大流量取边的容量和上个点提供的最大流量的较小值.

当到达汇点时,自然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])=min(delta,r[pre[i]][i]);

                for 
(i=t;i!=s;i=pre[i])[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]==-1) return -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}


最大流的建图思想:对于每条边,直接累加其容量就可以了。