Skip to content

求最大流的使用距离标号的最短增广路算法

求最大流有一种经典的算法,就是每次找增广路时用BFS找,保证找到的增广路是弧数最少的,也就是所谓的Edmonds-Karp算法。可以证明的是在使用最短路增广时增广过程不超过V*E次,每次BFS的时间都是O(E),所以Edmonds-Karp的时间复杂度就是O(V*E^2)。

如果能让每次寻找增广路时的时间复杂度降下来,那么就能提高算法效率了,使用距离标号的最短增广路算法就是这样的。所谓距离标号,就是某个点到汇点的最少的弧的数量(另外一种距离标号是从源点到该点的最少的弧的数量,本质上没什么区别)。设点i的标号为D[i],那么如果将满足D[i]=D[j]+1的弧(i,j)叫做允许弧,且增广时只走允许弧,那么就可以达到“怎么走都是最短路”的效果。每个点的初始标号可以在一开始用一次从汇点沿所有反向边的BFS求出,问题就是如何在增广过程中维护这个距离标号。

维护距离标号的方法是这样的:当找增广路过程中发现某点出发没有允许弧时,将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最小值加一。这种维护距离标号的方法的正确性我就不证了。

由于距离标号的存在,由于“怎么走都是最短路”,所以就可以采用DFS找增广路,用一个栈保存当前路径的弧即可。当某个点的距离标号被改变时,栈中指向它的那条弧肯定已经不是允许弧了,所以就让它出栈,并继续用栈顶的弧的端点增广。为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧,还有一种在常数上有所优化的写法是改变距离标号时把当前弧设为那条提供了最小标号的弧。当前弧的写法之所以正确就在于任何时候我们都能保证在邻接表中当前弧的前面肯定不存在允许弧。

还有一个常数优化是在每次找到路径并增广完毕之后不要将路径中所有的顶点退栈,而是只将瓶颈边以及之后的边退栈,这是借鉴了Dinic算法的思想。注意任何时候待增广的“当前点”都应该是栈顶的点的终点。这的确只是一个常数优化,由于当前边结构的存在,我们肯定可以在O(n)的时间内复原路径中瓶颈边之前的所有边。

我程序做了很多优化(甚至包括动态内存分配的时候使用placement new进行优化),最终能做到比wxs写的dinic快了大约一倍,但程序只有一百二十多行,我对这个结果很满意。距离标号增广的确是一个非常优秀非常实用的算法。

示例程序(USACO Training Ditch):
ditch.cpp

5 Comments

  1. herolilong wrote:

    大牛
    orz。。。
    为啥我学网络流就学得这么郁闷呢?
    老是错误不断。。
    可以给我看看你的sap代码么?
    (我的邮箱是herolilonghero@21cn.com)
    不胜感激!!!

    Tuesday, December 9, 2008 at 01:41 | Permalink
  2. herolilong wrote:

    为啥附件下载下来是乱码呢?

    Tuesday, December 9, 2008 at 01:58 | Permalink
  3. tianyi wrote:

    @herolilong
    打开那个链接会出现一个网页,上面有Download file链接。

    Tuesday, December 9, 2008 at 02:36 | Permalink
  4. xxx_stu wrote:

    看了这篇文章,比之前看的很多死人理论文章都感觉要好。谢谢DD大牛。

    Friday, March 18, 2011 at 16:38 | Permalink
  5. Mosey wrote:

    虽然年代已经有点久远了,但是还是从博客收获很多,感谢!但是代码链接貌似已经不能用啦o(╥﹏╥)o

    Wednesday, November 21, 2018 at 15:35 | Permalink

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*