设为首页 收藏本站
查看: 804|回复: 0

[经验分享] sap算法详解与模板 [转]

[复制链接]

尚未签到

发表于 2015-9-17 11:35:17 | 显示全部楼层 |阅读模式
  链接:
  1. Maximum Flow: Augmenting Path Algorithms Comparison

    http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=maxFlowRevisited
  2. 刘汝佳《算法艺术与信息学竞赛》 P321 ( 注: 上面的代码似乎有误,retreat()部分未回退< 详见下文or 链接1. > )

  ---------------------------------------------
  
  关键概念与性质:
  距离函数(distance function),我们说一个距离函数是有效的当且仅当满足有效条件(valid function)
  (1)d(t)= 0;
  (2)d(i)<= d(j)+1(如果弧rij在残余网络G(x)中);
  
  性质1:
  如果距离标号是有效的,那么d(i)便是残余网络中从点i到汇点的距离的下限;
  证明:



令任意的从i结点到汇点t长度为k的路径,设为i= i1-i2-i3...ik+1=t  
d(i(k)) <= d(i(k+1))+1= d(t)+1=1  
d(i(k-1)) <= d(i(k))+1=2  
d(i(k-2)) <= d(i(k-1))+1=3  
            :  
            :  
d(i(1)) <= d(i(2))+1= k  
由此可见,d(i)是i到t的距离下限     
  
  
  
  性质2:
  允许弧(边):如果对于边rij>0,且d(i)= d(j)+1,那么称为允许边
  允许路:一条从源点到汇点的且有允许弧组成的路径
  允许路是从源点到汇点的最短增广路径。
  证明:
  (1)因为rij>0所以必然是一条增广路
  (2)假设路径p是一条允许路包含k条弧,那么由d(i) = d(j)+1 可知,d(s)= k;
  又因为d(s)是点s到汇点的距离下限,所以距离下限为k,所以p便是一条最短路。
  
  性质3:
  在sap算法中距离标号始终是正确,有效的。并且每次的冲标号都会是距离标号严格递增
  证明:略;
  
  
  伪代码:



//algorithm shortest augment path;  
void sap()  
    {  
        x =0;  
        obtain the exact distance labels d(i);  
        i = s;  
        while (d(s)<n)  
        {  
            if (i has an admissible arc)  
            {  
                advance(i);  
                if (i == t)  
                {  
                    augment;  
                    i = s;  
                }  
            }  
            else  
                retreat(i);  
        }  
    }  
    //procedure advance(i);  
void advance(i)  
    {  
        let(i,j)be an admissible arc in A(t);  
        pre(j) = i;  
        i = j;  
    }  
    //procedure retreat  
void retreat()  
    {  
        d(i) = min(d(j)+1):rij>0;  
        if (i != s)  
            i = pre(i);  
    }  
  
  
  代码模板:



    #include <iostream>  
    #include <cstring>  
    #include <cstdlib>  
    usingnamespace std;  
    constint Max =225;  
    constint oo =210000000;  
    int n,m,c[Max][Max],r[Max][Max],c1[Max][Max],source,sink;  
    //c1是c的反向网络,用于dis数组的初始化  
int dis[Max];  
    void initialize()// 初始化dis数组  
    {  
         int q[Max],head =0, tail =0;//BFS队列  
         memset(dis,0,sizeof(dis));  
         q[++head] = sink;  
         while(tail < head)  
         {  
             int u = q[++tail], v;  
             for(v =0; v <= sink; v++)  
             {  
                   if(!dis[v] && c1[v] >0)  
                   {  
                       dis[v] = dis +1;  
                       q[++head] = v;  
                   }  
             }  
         }  
    }  
    int maxflow_sap()  
    {  
        initialize();  
        int top = source, pre[Max], flow =0, i, j, k, low[Max];  
        // top是当前增广路中最前面一个点。  
        memset(low,0,sizeof(low));//low数组用于保存路径中的最小容量  
while(dis[source] < n)  
        {  
            bool flag =false;  
            low[source] = oo;  
            for(i =0; i <= sink; i++)//找允许弧,根据允许弧的定义  
            {  
                if(r[top] >0&& dis[top] == dis +1)  
                {  
                    flag =true;  
                    break;  
                }  
            }  
            if(flag)// 如果找到允许弧  
            {  
                low = r[top];  
                if(low > low[top]) low = low[top];//更新low  
                pre = top; top = i;  
                if(top == sink)// 如果找到一条增广路了  
                {  
                    flow += low[sink];  
                    j = top;  
                    while(j != source)// 路径回溯更新残留网络  
                    {  
                        k = pre[j];  
                        r[k][j] -= low[sink];  
                        r[j][k] += low[sink];  
                        j = k;  
                    }  
                    top = source;//从头开始再找最短路  
                    memset(low,0,sizeof(low));  
                }  
            }  
            else// 如果没有允许弧  
            {  
                int mindis =10000000;  
                for(j =0; j <= sink; j++)//找和top相邻dis最小的点  
                {  
                    if(r[top][j] >0&& mindis > dis[j] +1)  
                        mindis = dis[j] +1;  
                }  
                dis[top] = mindis;//更新top的距离值  
if(top != source) top = pre[top];// 回溯找另外的路  
            }  
        }  
        return(flow);  
    }  
  
  
  
  运用gap优化:
  即当标号中出现了不连续标号的情况时,即可以证明已经不存在新的增广流,此时的流量即为最大流。
  简单证明下:
  假设不存在标号为k的结点,那么这时候可以将所有的结点分成两部分,一部分为d(i)>k,另一部分为d(i)<k
  如此分成两份,因为性质2可知,允许路为最短的增广路,又因为不存在从>k部分到<k部分的增广流,那么有最
  大流最小割定理可知此时便是最大流。
  
  优化代码:要注意在标号的时候不能直接把所有的初始为0,而应该为-1,否则会在gap优化的时候出现问题,不满足递增的性质,切记!
  
  



    #include <iostream>  
    #include <cstring>  
    #include <cstdlib>  
    usingnamespace std;  
    constint Max =225;  
    constint oo =210000000;  
    int n,m,c[Max][Max],r[Max][Max],c1[Max][Max],source,sink;  
    int gap[Max];//用gap来记录当前标号为i的个数,用于gap优化;  
    //c1是c的反向网络,用于dis数组的初始化  
int dis[Max];  
    void initialize()// 初始化dis数组  
    {  
        memset(gap,0,sizeof(gap));  
        int q[Max],head =0, tail =0;//BFS队列  
        memset(dis,0xff,sizeof(dis));  
        q[++head] = sink;  
        dis[sink] =0;  
        gap[0]++;  
        while(tail < head)  
        {  
            int u = q[++tail], v;  
            for(v =0; v <= sink; v++)  
            {  
                if(!dis[v] && c1[v] >0)  
                {  
                    dis[v] = dis +1;  
                    gap[dis[v]]++;  
                    q[++head] = v;  
                }  
            }  
         }  
    }  
    int maxflow_sap()  
    {  
        initialize();  
        int top = source, pre[Max], flow =0, i, j, k, low[Max];  
        // top是当前增广路中最前面一个点。  
        memset(low,0,sizeof(low));//low数组用于保存路径中的最小容量  
while(dis[source] < n)  
        {  
            bool flag =false;  
            low[source] = oo;  
            for(i =0; i <= sink; i++)//找允许弧,根据允许弧的定义  
            {  
                if(r[top] >0&& dis[top] == dis +1&&dis>=0)  
                {  
                    flag =true;  
                    break;  
                }  
            }  
            if(flag)// 如果找到允许弧  
            {  
                low = r[top];  
                if(low > low[top])   
                    low = low[top];//更新low  
                pre = top; top = i;  
                if(top == sink)// 如果找到一条增广路了  
                {  
                    flow += low[sink];  
                    j = top;  
                    while(j != source)// 路径回溯更新残留网络  
                    {  
                        k = pre[j];  
                        r[k][j] -= low[sink];  
                        r[j][k] += low[sink];  
                        j = k;  
                    }  
                    top = source;//从头开始再找最短路  
                    memset(low,0,sizeof(low));  
                }  
            }  
            else// 如果没有允许弧  
            {  
                int mindis =10000000;  
                for(j =0; j <= sink; j++)//找和top相邻dis最小的点  
                {  
                    if(r[top][j] >0&& mindis > dis[j] +1&&dis[j]>=0)  
                        mindis = dis[j] +1;  
                }  
                gap[dis[top]]--;  
                if (gap[dis[top]] ==0)  
                    break;  
                gap[mindis]++;  
                dis[top] = mindis;//更新top的距离值  
if(top != source) top = pre[top];// 回溯找另外的路  
            }  
        }  
        return(flow);  
    }  
  
  
  
  
  注意:在运用sap的时候必须要时刻的保证标号的两个性质,因此,不能再初始标号的时候将全部初始为0层,因为有些点是不具有层数的,或者说是层数是无穷大的,不可达的。
  
  网上找的比较清晰地模板sap,有各种优化:
  



    #include <stdio.h>  
    #include <string.h>  
    #define INF 2100000000  
    #define MAXN 301  
    int SAP(int map[][MAXN],int v_count,int s,int t)      //邻接矩阵,节点总数,始点,汇点  
    {  
        int i;  
        int cur_flow,max_flow,cur,min_label,temp;         //当前流,最大流,当前节点,最小标号,临时变量  
char flag;                                        //标志当前是否有可行流  
int cur_arc[MAXN],label[MAXN],neck[MAXN];         //当前弧,标号,瓶颈边的入点(姑且这么叫吧)  
int label_count[MAXN],back_up[MAXN],pre[MAXN];    //标号为i节点的数量,cur_flow的纪录,当前流路径中前驱  
        //初始化  
        memset(label,0,MAXN*sizeof(int));  
        memset(label_count,0,MAXN*sizeof(int));  
        memset(cur_arc,0,MAXN*sizeof(int));  
        label_count[0]=v_count;                           //全部初始化为距离为0  
      
        neck=s;  
        max_flow=0;  
        cur=s;  
        cur_flow=INF;  
        //循环代替递归  
while(label<v_count)  
        {  
            back_up[cur]=cur_flow;  
            flag=0;  
            //选择允许路径(此处还可用邻接表优化)  
for(i=cur_arc[cur];i<v_count;i++)    //当前弧优化  
            {  
               if(map[cur]!=0&&label[cur]==label+1)    //找到允许路径  
               {  
                   flag=1;  
                   cur_arc[cur]=i;    //更新当前弧  
if(map[cur]<cur_flow)    //更新当前流  
                   {  
                       cur_flow=map[cur];  
                       neck=cur;     //瓶颈为当前节点  
                   }  
                   else  
                   {  
                       neck=neck[cur];     //瓶颈相对前驱节点不变  
                   }  
                   pre=cur;    //记录前驱  
                   cur=i;  
                   if(i==t)    //找到可行流  
                   {  
                       max_flow+=cur_flow;    //更新最大流  
                       //修改残量网络  
while(cur!=s)  
                       {  
                           if(map[pre[cur]][cur]!=INF)map[pre[cur]][cur]-=cur_flow;  
                           back_up[cur] -= cur_flow;  
                           if(map[cur][pre[cur]]!=INF)map[cur][pre[cur]]+=cur_flow;  
                           cur=pre[cur];  
                       }  
                       //优化,瓶颈之后的节点出栈  
                       cur=neck[t];  
                       cur_flow=back_up[cur];   
                   }  
                   break;  
               }  
            }  
            if(flag)continue;  
            min_label=v_count-1;    //初始化min_label为节点总数-1  
            //找到相邻的标号最小的节点     
for(i=0;i<v_count;i++)  
            {  
                if(map[cur]!=0&&label<min_label)  
                {  
                    min_label=label;  
                    temp=i;  
                }  
            }  
            cur_arc[cur]=temp;    //记录当前弧,下次从提供最小标号的节点开始搜索  
            label_count[label[cur]]--;    //修改标号纪录  
if(label_count[label[cur]]==0)break;    //GAP优化  
            label[cur]=min_label+1;    //修改当前节点标号  
            label_count[label[cur]]++;     //修改标号记录  
if(cur!=s)  
            {  
               //从栈中弹出一个节点  
               cur=pre[cur];  
               cur_flow=back_up[cur];  
            }  
        }  
        return(max_flow);  
    }  
  
  
  【转自】
  http://blog.iyunv.com/liguanxing/article/details/5783804
  
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-114900-1-1.html 上篇帖子: SAP Table and Field Search Stratagies 下篇帖子: SAP之SD主数据
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表