`

最大流(二)——SAP算法

 
阅读更多

找了半天,“百度百科”上这篇文章还能比较直观地帮我理解SAP算法:
 
求最大流有一种经典的算法,就是每次找增广路时用BFS找,保证找到的增广路是弧数最少的(即边权都为1时的最短路径),也就是所谓的Edmonds-Karp算法。可以证明的是在使用最短路增广时增广过程不超过|V|*|E|次,每次BFS的时间都是O(|E|),所以Edmonds-Karp的时间复杂度就是O(|V|*|E|^2)。
 
如果能让每次寻找增广路时的时间复杂度降下来,那么就能提高算法效率了,使用距离标号d(有时候称为“高度h”)的最短增广路算法就是这样的。所谓距离标号 ,就是某个点到汇点的最少的弧的数量(即边权值为1时某个点到汇点的最短路径长度) 。设点i的标号为d[i],那么如果将满足d[i]=d[j]+1的弧(i,j)叫做允许弧 ,且增广时只走允许弧,那么就可以达到“怎么走都是最短路”的效果 。每个点的初始标号可以在一开始用一次从汇点沿所有反向边的BFS求出,实践中可以初始设全部点的距离标号为0,问题就是如何在增广过程中维护这个距离标号。

 

 

Sam评注:
(1).
    外国大牛的文章http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=maxFlowRevisited#1 中有这样一句话非常重要:“We can distance function exactly if each i in V, d[i] equals the length of the shortest path from i to t(sink) in the residual network.”
    上面这句话意思是说:只有当d[i]恰好等于i到汇点t的最短路径(边权都为1)时,才称d为“距离函数”。而每次用SAP寻找增广路径初始,d[i] (i∈V)都被初始化为0。因此一开始,只有汇点的距离函数d[t]=0满足这个条件;对其他顶点,d[i]只能称为“i的距离函数值的下界/估计”,因为它<真正的d[i]。于是,外国大牛又冒出这样一句话:
“It is also easy to prove that if d(s)≥|V|, then the residual network contains no path from the source to the sink.”
(2).
    寻找一条最短(边最少)增广路径中用到的“递归机制”,实际上保证了最短路径的层次图是从汇点向源点扩展的。而每次用SAP寻找一条增广路径开始时,d[t]=0已经是真正的最短路径,因此如果有最短增广路径,完成SAP搜索后d[i]=真正的最短路径。

void  find_path_sap(int  cur){
           ...
           find_path_sap(i);
           ...
}
 

维护距离标号的方法是这样的:当找增广路过程中发现某点出发没有允许弧时,将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最小值加一。这种维护距离标号的方法的正确性我就不证了。由于距离标号的存在,由于“怎么走都是最短路”,所以就可以采用DFS找增广路,用一个栈保存当前路径的弧即可。当某个点的距离标号被改变时,栈中指向它的那条弧肯定已经不是允许弧了,所以就让它出栈,并继续用栈顶的弧的端点增广。为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧,还有一种在常数上有所优化的写法是改变距离标号时把当前弧设为那条提供了最小标号的弧。当前弧的写法之所以正确就在于任何时候我们都能保证在邻接表中当前弧的前面肯定不存在允许弧。
 
还有一个常数优化是在每次找到路径并增广完毕之后不要将路径中所有的顶点退栈,而是只将瓶颈边以及之后的边退栈,这是借鉴了Dinic算法的思想。注意任何时候待增广的“当前点”都应该是栈顶的点的终点。这的确只是一个常数优化,由于当前边结构的存在,我们肯定可以在O(n)的时间内复原路径中瓶颈边之前的所有边。
 
几个重要优化:
 
1.邻接表优化:
如果顶点多的话,往往N^2存不下,这时候就要存边:
存每条边的出发点,终止点和价值,然后排序一下,再记录每个出发点的位置。以后要调用从出发点出发的边时候,只需要从记录的位置开始找即可(其实可以用链表)。优点是时间加快空间节省,缺点是编程复杂度将变大,所以在题目允许的情况下,建议使用邻接矩阵。
 
2.GAP优化:
如果一次重标号时,出现距离断层,则可以证明ST无可行流,此时则可以直接退出算法。
 
3.当前弧优化:
为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧。
 
学过之后又看了算法速度的比较,发现如果写好的话SAP的速度不会输给HLPP。

 

在前面的SAP程序加入一些注释,观察增广过程:

/*
测试用例: 
6 5
1 2 2
1 3 5
2 4 1
3 4 6
3 5 1
4 5 5
*/
#include<stdio.h>  
#include<string.h>  
#include <iostream>
using namespace std;

#define INF 1<<29;  
  
struct sap  
{  
    int s,t;    //s:源点 t:汇点  
    int m;      //顶点数  
    int cf[305][305];   //残留容量  
  
    int flow;       //当前最大流  
    int cf_path;    //本次可增广的流量(本次增广路径的残留容量)
    bool flag;      //本次增广路径是否找到  
    int vh[1000];   //各高度的顶点个数  
    int h[1000];    //各顶点的高度,又称为“距离标号”——到汇点距离的下界(边权值都为1)
  
    sap(){  
        flow =cf_path =s =t =0;  
        flag=false;  
        memset(cf,0,sizeof(cf));  
        memset(vh,0,sizeof(vh));  
        memset(h,0,sizeof(h));  
    }  
  
    /*find_path_sap(int cur):增广路径已经推进到顶点cur,尝试向下一个顶点推进
       主要思路:
             递归地尝试沿着当前点cur的所有允许弧推进增广路径。
             如果失败,将当前点cur的高度(距离函数下界)更新到minH(minH:残留网络中所有cur指向节点的最小高度)
    */
    void find_path_sap(int cur){  
        //1. 检查是否走到增广路径末端  
        //*a. 终止条件:cur是本次增广路径末端节点(汇点)  
        if(cur==t){  
            flow+=cf_path;  
            flag=true;  
            return;  
        }  
  
        //2. 尝试递归/回溯 找增广路径  
        //如果cur仅比邻接点高1,则尝试最大限度向它送出流  
        int i;  
        int minH=m-1;  
        int tmp_cf_path=cf_path;  
        for(i=1;i<=m;i++){  
            if(cf[cur][i]){  
                if(h[i]+1==h[cur]){  
                    if(cf[cur][i]<cf_path)  
                        cf_path=cf[cur][i];  
                    find_path_sap(i);  
                      
                    //*b. 终止条件: 如果h[1]增长到m,表示通过本次"find_path_sap(i)"递归搜索,不能找到一条增广路径  
                    if(h[1]>=m)    
                        return;  
  
                    //*c. 终止条件: 通过本次"find_path_sap(i)"递归搜索,找到了一条增广路径  
                    if(flag)  
                        break;  
  
                    //通过本次"find_path_sap(i)"递归没有找到一条增广路径,回溯时需要恢复状态  
                    cf_path=tmp_cf_path;   
                }  
                if(h[i]<minH)  
                    minH=h[i];  
            }  
        }  
        //以上for()执行完,minH为cur的邻接顶点中高度最小的点的高度  
  
        //3. 检查回溯结果  
        //   成功:逆着增广路径压入增广的流  
        //   失败:顶点cur高度增加1  
        if(flag){  
            //上面代码中的某次递归成功找到一条增广路径(从cur找下去,找到一条增广路径)  
            cf[cur][i]-=cf_path;  
            cf[i][cur]+=cf_path;  
        }else{  
            vh[h[cur]]--;  
            if(vh[h[cur]]==0)  
                h[1]=m;     //作用到终止条件"*b"  

            h[cur]=minH+1;  
            vh[h[cur]]++;  
        }  
    }  
      
    //Ford-Fulkerson算法框架  
    int solve(){  
        vh[0]=m;  
        flow=0;

        int cnt=1;
		//h[1]保持<m,一旦增长到m,就不再存在任何增广路径
        while(h[1]<m)  
        {  
            flag=false;
            cf_path=INF;
            find_path_sap(s);
            
            cout<<"第"<<cnt++<<"次: "<<flow<<endl;
            for(int i=1;i<=m;i++){
                for(int j=1;j<=m;j++){
                    if(cf[i][j]>0){
                        cout<<"cf["<<i<<","<<j<<"]= "<<cf[i][j]<<endl;
                    }
                }
            }
            
            for(int i=1;i<=m;i++){
                cout<<"h["<<i<<"]"<<"\t";
            }
            cout<<endl;
            for(int i=1;i<=m;i++){
                cout<<h[i]<<"\t";
            }
            cout<<endl;
            cout<<"-----------------------"<<endl;
        }  
        return flow;  
    }  
      
    void addEdge(int x,int y,int c){  
            cf[x][y]+=c;  
    }  
      
};  
  
int main(){  
    int n;	//有向边数
	int m;	//顶点数
    while(scanf("%d %d",&n,&m)!=-1)  
    {  
        sap nt= sap();  
        nt.s=1;  
        nt.t=m;  
        nt.m=m;  
        for(int i=1;i<=n;i++)  
        {  
            int x,y,c;  
            scanf("%d %d %d",&x,&y,&c);  
            nt.addEdge(x,y,c);  
        }  
        printf("%d\n",nt.solve());  
    }  
    return 0;  
} 
 
分享到:
评论

相关推荐

    一种最大流算法——SAP

    ### 最大流算法——SAP算法详解 #### 1. 引言 在网络流理论中,最大流问题是一项核心研究内容。它旨在寻找一个网络中从源点到汇点的最大流量值,这个问题不仅在计算机科学领域有着广泛的应用,如网络传输、任务调度...

    网络流sap算法(whitecloud)

    通过这种方式,SAP算法能够在较短的时间内找到最大流。 综上所述,SAP算法通过引入距离标号和Gap优化等机制,极大地提高了最大流问题的求解效率。在实际应用中,尤其是在处理大规模网络流问题时,SAP算法展现出了其...

    SAP采购价格条件技术——初学者必看.pdf

    SAP采购价格条件技术 SAP采购价格条件技术是SAP系统中的一种重要的技术模块,它主要用于管理和维护采购价格条件的设置和应用。该技术模块可以帮助企业更好地管理采购价格条件,提高采购效率和降低成本。 SAP采购...

    生产排程之启发式算法

    本章主要探讨了如何使用一种特定类型的元启发式算法——分布估计算法(EDA)来解决带有顺序依赖家族设置时间的流水线车间调度问题。 - **1.1 引言**:介绍了流水线车间调度问题的基本概念以及其在实际生产中的重要...

    abap简单递归算法

    本文将深入探讨一个ABAP中的简单递归算法——计算阶乘,并通过SE38报表的形式进行展示。 #### 二、递归算法简介 递归算法是一种通过调用自身来解决问题的方法。它通常用于解决那些可以通过子问题的解来构建整个...

    SAP中文使用手册——PP生产订单

    #### 二、SAP生产订单简介 ##### 1. 总览 SAP生产订单是生产过程中的一项关键任务,它定义了生产活动的具体细节,包括所需物料、工艺路线、计划日期等信息。通过生产订单,企业可以有效地管理生产进度、物料消耗以及...

    解决局部极端问题的新方法

    为了解决传统图像配准方法中存在的计算效率低、配准时间长、易出现局部极值、配准精度不高等问题,本文提出了一种新的图像配准算法——SAP算法。 SAP算法结合了模拟退火算法(Simulated Annealing Algorithm)的高...

    tsm_配置_for_SAP

    针对 SAP 环境中的数据保护需求,TSM 提供了专门的解决方案——Tivoli Storage Manager for Enterprise Resource Planning Data Protection for SAP,以确保 SAP 数据的安全性和高可用性。 #### 二、TSM for SAP ...

    SAP ABAP 开发 BOM

    标题和描述均提到了"SAP ABAP开发BOM",这指向了SAP系统中一个核心功能——物料清单(Bill of Materials,简称BOM)的开发与管理,尤其是在使用ABAP(Advanced Business Application Programming,高级商业应用编程...

    SAP集团财务解决方案

    #### 二、SAP集团财务解决方案特点研讨——集团财务核算自动化 SAP集团财务解决方案通过高度自动化的财务核算系统,显著提升了财务工作效率。该解决方案包括: 1. **自动化记账**:通过预设的规则和模板,实现日常...

    大数据应用基础-分类算法115.pptx

    大数据人才主要分为分析人才和架构人才,而分析人才的需求量最大,他们的工作核心在于数据挖掘。 在大数据的核心——非结构化数据处理中,面临的主要挑战是如何收集、集成及分析这些数据。例如,从各种设备和渠道...

    SAP_MEs_完美工厂—操作性极强的智能工具将企业战略直接贯彻到车间层.zip

    《SAP MES:完美工厂——将企业战略深入到车间层的智能工具》 SAP MES(制造执行系统)是企业信息化建设中的关键组件,尤其在制造业中,它扮演着连接企业战略与生产现场的重要角色。SAP MES系统以其强大的操作性和...

    超级好经典计算机面试——我的求职经验.pdf

    1. 公司名称:文档中提到了许多国际知名的IT公司和金融机构,比如UBS、BNP Paribas、CICC、KPMG、Moody's、EMC、VMware、IBM、Intel、Amazon、SAP、NHN Websense、Open Question等。了解这些公司的业务范围、企业...

    sap消费品客户关系管理

    SAP为消费品行业提供了一种集成式的客户关系管理(CRM)解决方案——mySAP CRM。该解决方案旨在帮助企业更高效地管理客户关系,实现从前台到后台办公软件的全方位整合,无论是SAP自家的产品还是来自第三方供应商的...

    revit在桥梁中的应用二次开发篇(钢筋)(Word资料)

    在文档"revit在桥梁中的应用二次开发篇(2)——钢筋.docx"中,读者可以期待找到更多关于如何利用Revit API进行桥梁钢筋建模的实例和代码示例。这些内容将涵盖从基础的API概念到实际编程技巧,帮助开发者逐步掌握Revit...

    XXXX信息管理MBA课程三——数据挖掘与商务智能.pptx

    从早期的SPSS,到现在的R、Python、SAS等工具,以及专门的数据挖掘平台如IBM SPSS Modeler和SAP Lumira,它们为非专业人员提供了易于使用的界面,使得数据挖掘更加普及和高效。 商业智能是数据挖掘的应用层面。通过...

    Data Structure and Algorithmic Thinking with Python -- 2016.pdf

    ### 数据结构与算法思维——Python实现 #### 一、引言 《数据结构与算法思维——Python实现》是由Narasimha Karumanchi编著的一本书籍,于2016年由CareerMonk出版社出版。该书主要针对的是那些希望深入理解数据结构...

    Althoff -- The Self-Taught Computer Scientist -- 2021.pdf

    ### 自学计算机科学家——阿尔托夫的初学者指南:数据结构与算法 #### 知识点一:自学计算机科学的重要性及方法论 - **自学计算机科学的重要性**:随着信息技术的快速发展,越来越多的人意识到掌握计算机科学基础...

    ABAP加密和解密.doc

    这些方法支持对字符串、二进制数据甚至整个表进行加密。 解密则是加密的逆过程,将密文还原为原始明文。在ABAP中,对应的解密函数如`cl_abap_decrypt`类可以用来恢复加密的数据。使用这些函数时,需要确保正确地...

    Data Structure and Algorithmic Thinking with Python

    ### 数据结构与算法思维——Python实现 #### 一、引言 《数据结构与算法思维——Python实现》是由Narasimha Karumanchi撰写的一本深入探讨数据结构与算法的书籍,出版于2020年。本书不仅涵盖了基础的数据结构与算法...

Global site tag (gtag.js) - Google Analytics