网络流sap算法(whitecloud)

大学在ACM期间,写过几篇解题报告,这是其中一篇上传在百度文库,开通了CSDN博客之后,就把这篇解题报告拿过来当做自己第一篇博客!

概述:

最短增广路算法(Shortest Augmenting Path Algorithm),即每次寻找包含弧的个数最少的增广路进行增广,可以证明,此算法最多只需要进行mn/2次增广。并且引入距离标号的概念,可以在O(n)的时间里找到一条最短增广路。最终的时间复杂度为O(n^2m),但在实践中,时间复杂度远远小于理论值(特别是加了优化之后),因此还是很实用的。

1)距离标号:

对于每个顶点i赋予一个非负整数值dis(i)来描述i到t的“距离”远近。

初始化的时候,所有的顶点的dis[i]的值均为0;

 

2)允许弧和允许路:

如果残留网络G中的一条弧(i,j)满足dis(i)=dis(j)+1,我们称(i,j)是允许弧,由允许弧组成的一条s-t路径是允许路。显然,允许路是残留网络G中的一条最短增广路。当找不到允许路的时候,我们需要修改某些点的dis(i)。

eg:增光路径:src->2->sink(以下的说明只用这三个顶点说明如果找到增广路)

1:dis[src]=0;  没有增广路径 修改dis[src]=1;

2:dis[src]=1; dis[src]=dis[2]+1; dis[2]=0,没有增广路径,修改:dis[2]=1;

 

 

3: dis[src]=1; 接着在修改的值dis[src]=dis[2]+1=2;

4:这样就找到例子的增广路径了:src->2->sink;

                       dis:2    1   0 (严格按照允许弧的定义)

特别说明:你只需要知道这条路径是怎样找出来的时候就可以了,具体如何实现我在后面将用结合代码图片讲解。                

3)Gap优化:(这个数组的作用判断残留网络来还有没有增广路径,相当于搜索的剪枝)

Gap[x]=y:说明残留网络中dis[i]=x的个数为y,一样好好理解这句话,不然后面的好难看懂 

我们可以注意到由于残留网络的修改只会使dis(i)越来越大(因为修改前dis(i)<dis(j)+1,而修改后会存在dis(i)=dis(j)+1,因此变大了),所以说dis(i)是单调递增的,这就提示我们,如果dis函数出现了“断层”,即没有dis(i)=k,而有dis(i)=k±1,这时候必定无法再找到增广路径。我们可以这么想,现在的i满足dis(i)=k+1,发现没有一个dis(j)为k,因此就会尝试去调整dis(i),但是dis(i)是单调递增的,只会越来越大,所以k这个空缺便永远不会被补上,也就是说无法再找到增广路径。(这个地方一定要想明白,本算法经典所在)。

本人用的是递归实现sap算法的,从理解和代码量来说都比非递归简单,缺陷就是时间长了那么一点点:但是我想说的是,咱们有的是时间,不差时间!其实sap递归算法优化的好的话,其实也蛮快的。

就拿这个光着身子的例题开刀:http://acm.hdu.edu.cn/showproblem.php?pid=3549

int maxflow_sap(int src,int ed)  //传递参数为源点和汇点
{
    int ans(0);
    clr(dis); clr(gap);      //define clr(p)  memst(p,0,sizeof(p))
    gap[0]=Vs=ed;           //这个地方gap[0]和Vs分别是顶点的个数,有
                            //候是ed+1…… ,反正就等于顶点的个数
                            //gap[0]=Vs:说明此事有Vs个dis[i]=0
    source=src; sink=ed; 
while(dis[source]<Vs)
        ans+=dfs(source,inf);  //厉害的就是这个dfs函数,从源点出发,
                              //第二个参数就是流量,从源点出发,相当
//一个水库出发
    return ans;
}
/*
struct E
{
    int to,val,next;
         to:下个节点是什么(可以从深度上理解,可以理解to的都是子节点)
         val:当前节点的流量  
         next:下一个节点(可以从广度上理解,可以理解next的都是兄弟节点)
         比如: 
};*/

//再次强调gap[0]和Vs的赋值

int dfs(int src,int aug)  //函数返回这条增广路径的流量
{
    if(src==sink)  return aug;  
    int flow=0,min_d=Vs-1; 
                       //flow这条路径的所有增流量(注意是所有的)
                       //min_d就和这条边相连接的所有顶点的最小
                       //dis的值
    for(int j=list[src]; j!=-1;j=edg[j].next) 
       if(edg[j].val)   
       {
           if(dis[src]==dis[edg[j].to]+1)   //允许弧
           {

              int t=dfs(edg[j].to,MIN(aug-flow,edg[j].val)) 
               //t保存了这条路径的增量,
               // MIN(aug-flow,edg[j].val):网络流特性决定的
 
 
               edg[j].val-=t;       //更新网络流  
               edg[j^1].val+=t;     
               flow+=t;              //把所有的增量加起来 

 

             if(dis[source]>=Vs) return flow;
              //如果出现这种情况,说明已经没有增光路径了,返回此时的   
              //流量就可以了                   
               if(aug==flow)  break;  
                //这是一个减枝的作用,如果src这个管子的aug都用完了, 
               //后面就算有增广路径,增加的只是一个0,索性跳出循环
               //节省了时间
           }
           min_d=MIN(min_d,dis[edg[j].to]);
            //找到和src链接的所有点中dis最小的值;如果从src出发有增  
            //量的话,则不需要更新dis函数值,这怎么办呢?看后面
       }
       if(!flow)   //看到这个判断了没有,flow其实有两个作用:
                   //1:保存路径的所有流量;
                  //2:判断从src出发能不能找到路径
       {
            //这说明flow=0,这有说明了什么了?不解释
           if(!(--gap[dis[src]])) 
            //分开写:--gap[dis[src];因为我要修改dis[src]的值,
            //                  显然要gap[dis[src]]要减去1
            // if(!gap[dis[src]])出现断层的现象了,不需要再找了,     
             dis[source]=Vs; //这个赋值就是算法结束的标志
          ++gap[dis[src]=min_d+1]; //不明白,自己展开
       }
       return flow;
}

到此:shortest augmenting path 算法介绍完毕(以我的理解就是这样的)。目前我做的题目用这个算法经本通过了,或许还有考虑不周到的地方,如果发现,或者对我的讲解有什么不清楚的地方,可与鄙人联系:381018143.

最后希望在校HYNUACMer对算法的学习,我们一起努力,来年争取为校增光!

最后附上完整的代码:

http://acm.hdu.edu.cn/showproblem.php?pid=3549

#include <stdio.h>
#include <string.h>
#define MAXV   16
#define MAXN   500
#define inf    0x3fffffff
#define MIN(a,b) a>b?b:a
#define clr(p) memset(p,0,sizeof(p))
 
struct E
{
    int to,val,next;
};
 
E edg[MAXN];
int G[MAXV][MAXV],dis[MAXV],gap[MAXV],list[MAXV],nodes;
int source,sink,Vs;
 
void addedg(int from,int to,int val)
{
    edg[nodes].to=to; edg[nodes].val=val; edg[nodes].next=list[from]; list[from]=nodes++;
    edg[nodes].to=from; edg[nodes].val=0; edg[nodes].next=list[to]; list[to]=nodes++;
}
 
int dfs(int src,int aug)
{
    if(src==sink)  return aug;
 
    int flow=0,min_d=Vs-1;
    for(int j=list[src]; j!=-1;j=edg[j].next)
       if(edg[j].val)
       {
           if(dis[src]==dis[edg[j].to]+1)
           {
               int t=dfs(edg[j].to,MIN(aug-flow,edg[j].val));
 
               edg[j].val-=t;
               edg[j^1].val+=t;
               flow+=t;
 
               if(dis[source]>=Vs) return flow;
               if(aug==flow)  break;
           }
           min_d=MIN(min_d,dis[edg[j].to]);
       }
       if(!flow)
       {
           if(!(--gap[dis[src]]))  dis[source]=Vs;
           ++gap[dis[src]=min_d+1];
       }
       return flow;
}
 
int maxflow_sap(int src,int ed)
{
    int ans(0);
    clr(dis); clr(gap);
    gap[0]=Vs=ed;
    source=src; sink=ed;
 
    while(dis[source]<Vs)
        ans+=dfs(source,inf);
    return ans;
}
int main()
{
    int CS,c; scanf("%d",&CS);
    for(c=1;c<=CS;++c)
    {
        clr(G);
        memset(list,-1,sizeof(list)); nodes=0;
        int n,m; scanf("%d %d",&n,&m);
        int i,j;
        for(i=1;i<=m;i++)
        {
            int x,y,c; scanf("%d %d %d",&x,&y,&c);
            G[x][y]+=c;
        }
 
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
{
               if(G[i][j])
   addedg(i,j,G[i][j]);
}
 
printf("Case %d: %d\n",c,maxflow_sap(1,n));
    }
    return 0;
}


在这道题目上没有体现出优势,可是在别的题目算法还是比较快的。

参考:http://hi.baidu.com/oimaster/blog/item/08145cd6b484972606088bc1.html

 

                                                                                                    WhiteCloude(原创)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值