网络最大流和最小费用流


from: http://richardxx.yo2.cn/articles/网络最大流和最小费用流.html

这段时间复习了下网络流模型,感觉比以前的理解有了长足进展,虽然我知道这东西难就难在建模上,而它的算法本身其实难度不大,但我还是决定说一些我的理解,毕竟理解了本质的东西运用起来才会更灵活。


最大流的求解一般有两类算法(用费用流附带求出的不列入考虑范围),就是增广路(FF)系列和预流推进(PF)系列。在很多地方都看到推荐使用后者,因为效率更高,但其实不然,今天我这篇文章就是要重点介绍前者,相信大家看了过后也会喜欢它的。


首先,那些一大堆的关于网络的定义我就不说了,不明白的就先去看看书好了,我们约定S是源,T是汇,G表示残余图。增广路算法其实相当好理解,因为只要在G中存在一条从S到T的通路,那沿这条路走,流量一定还可以增加,所以当求得最大流的时候一定不存在S-T通路。 那反过来成立么?显然,因为我们有最小割最大流定理,所以由此就得到了FF算法的基本方法:

不断的在G中找一条S-T通路,然后沿其增广,直到无法找到。


至于用什么方法去找一条路,那就随便了,BFS,DFS,甚至A*,你可以把你能想到的方法试个遍,看看哪个合胃口就用哪个吧。

注意那个用BFS的方法,它的官方名字叫Edmonds-Karp(EK),它和我们后面要说的费用流有很大的关系,这里先简单提示一下。

那这方法的复杂度呢?如果是整数流(事实上分数流的话可以构造数据让FF无法停止,这时候必须用到PF的方法),显然每次至少增广1的流量,每次查找O(E)条边,增广要O(V)的时间,因此复杂度为O((m+n)*U),U是最大流。

听上去很恐怖,其实EK算法的一个稍微精确的上界是O(VE^2),还是很夸张。

那么,怎样才能跑得更快呢?想想,每次用BFS求出的Shortest Path Tree(SPT),我们只关心了S-T最短路,其实还有很多信息没有利用到,那么如果求一次SPT,我们可以增广多次的话,是不是会好些??

于是,引入层次图的概念,说通俗点,就是求G的SPT,把G中每个顶点的距离S的标号d给求出来,保留所有的s->t中d[s]+1=d[t]的边(注意可以不是SPT中的边),最后再在层次图中用多次BFS把所有的增广路求出来增广。

这样看似更快了,其实没有,因为分析复杂度依然是O(VE^2),没有改进,而且我们还要写更多的代码。那瓶颈在哪里呢?对了,就是那步在层次图中用BFS来找增广路。其实更好的方法是用DFS,一次就可以把所有的增广路求出来,具体做法如下:

从S开始做DFS,一旦发现一条增广路,于是就沿其增广,然后DFS回退到离S最近的一条满流的边的起点处,并在图中删除该边的终点,继续搜索。

上面的算法就是著名的Dinic算法,它的复杂度为O(EV^2)(注意,看清楚2的位置),对于密图有不小的改进。

事实上,我非常推荐在各类比赛中用Dinic算法,因为它实现简单,而且实际运行速度快,比起PF算法好写太多了!!

那单纯的说好还是不行,要拿出依据来。关于PF算法的理论在本文中就不详细叙述了,可能以后我会专门介绍它。一般的PF实现是O(V^4)的,而Relable-To-Front是O(V^3),高标推进据称是O(V^2*sqrt(E)),虽然它实际中确实比较快,但是PF的算法都有一个缺点,就是必须加启发优化,至少是Gap优化,否则实际中会比较慢。因此,写一个PF的代码自然就比较多了。


上面基本上把最大流的算法盘点了一下,下面还是说一下最小费用流问题。

如果一个网络的边不仅有容量,还有单位流量费用的话,那我们自然想在求得最大流的同时,使总费用更低。因此,最小费用流实际上是一个更通用的模型,因而其应用也更广。

令人惊奇的是,解决该问题可以不需要先求出最大流来(当然也有方法需要),所以如果你不想记那么多算法的话只知道该算法也没问题。因为费用流的算法如果说复杂的话可以把线性规划扯近来,所以先罗列下几个基本方法,然后介绍最简单的一种,其它的如果你有兴趣可以自己找文章来看:

1、 连续最短路算法(Successive Shortest Path);

2、 消圈算法(Cycle Canceling);

3、 原始对偶算法(Primal Dual);

4、 网络单纯形(Network Simplex)。


后面的两个分别是前面两个的高级优化版本,其基本的优化思路就是和我们上面论述的优化增广类似,就是希望每次都能做几次运算,不要一次用了就丢弃了。最好用的就是第一个方法,方法二在Algorithm in C图论部分有详细论述,我就不说了。

还记得刚才我说的EK么?其实如果我们用Dijkstra在G中找S-T最短路,而不是BFS的话,是不是就解决问题了呢?是的,完全正确,我把证明留给大家想,其实很简单。

但是,有个问题,在G中存在负权的边,用Dijkstra是不是不行了呢?非要用Bellman-Ford么?其实不然,只要原图中没有负环存在(有的话不存在最小费用),那么我们可以利用Johnson算法的重赋权技术把所有的边先变成正权,然后以后每次增广后再维护一下不就可以用高效的Dijkstra了?算法如下:

用Bellman-Ford求各点到S的高度标号d[];

以后每求一次最短路,设标号为pi[],那么执行:

For i=1 to v do

d[v]+=pi[v]

这样一来,标号就一直合法了(具体证明还是留给大家吧)。


----------------------------------------------------------------------------------------

网络流ISAP算法的简单介绍

from: http://www.zlinkin.com/?p=34

这几天由于种种原因经常接触到网络流的题目,这一类型的题给人的感觉,就是要非常使劲的YY才能出来点比较正常的模型。尤其是看了Amber最小割应用的文章,里面的题目思路真是充满了绵绵不绝的YD思想。然而比赛中,当你YD到了这一层后,您不得不花比较多的时间去纠结于大量细节的实现,而冗长的代码难免会使敲错版后的调试显得异常悲伤,因此一些巧妙简短高效的网络流算法在此时便显得犹为重要了。本文力求以最简短的描述,对比较流行的网络流算法作一定的总结,并借之向读者强烈推荐一种效率与编程复杂度相适应的算法。

  众所周知,在网络流的世界里,存在2类截然不同的求解思想,就是比较著名的预流推进与增广路,两者都需要反向边的小技巧。

  其中预流推进的算法思想是以边为单元进行推流操作。具体流程如下:置初始点邻接边满流并用一次反向bfs对每个结点计算反向距离标号,定义除汇点外存量大于出量的结点为活动结点,每次对活动结点按允许边(u->v:d[u]=d[v]+1)进行推流操作,直到无法推流或者该点存量为0,若u点此时仍为活动结点,则进行重标号,使之等于原图中进行推操作后的邻接结点的最小标号+1,并将u点入队。当队列为空时,算法结束,只有s点和t点存量非0,网络中各顶点无存量,无法找到增广路继续增广,则t点存量为最大流。

  而增广路的思想在于每次从源点搜索出一条前往汇点的增广路,并改变路上的边权,直到无法再进行增广,此时汇点的增广量即为最大流。两者最后的理论基础依然是增广路定理,而在理论复杂度上预流推进要显得比较优秀。其中的HLPP高标预流推进的理论复杂度已经达到了另人发指的O(sqrt(m)*n*n),但是其编程复杂度也是同样的令人发指- -

  于是我们能否在编程复杂度和算法复杂度上找到一个平衡呢,答案是肯定的。我们使用增广路的思想,而且必须进行优化。因为原始的增广路算法(例如EK)是非常悲剧的。于是有人注意到了预流推进中的标号法,在增广路算法中引入允许弧概念,每次反搜残留网络得到结点标号,在正向增广中利用递归进行连续增广,于是产生了基于分层图的Dinic算法。一些人更不满足于常规Dinic所带来的提升,进而加入了多路分流增广的概念,即对同一顶点的流量,分多路同时推进,再加上比较复杂的手工递归,使得Dinic已经满足大部分题目的需要。

  然而这样做就是增广路算法优化的极限么?答案永远是不。人们在Dinic中只类比了预流推进的标号技术,而重标号操作却没有发挥得淋漓尽致。于是人们在Dinic的基础上重新引入了重标号的概念,使得算法无须在每次增广后再进行BFS每个顶点进行距离标号,这种主动标号技术使得修正后算法的速度有了不少提高。但这点提高是不足称道的,人们又发现当某个标号的值没有对应的顶点后,即增广路被截断了,于是算法便可以提前结束,这种启发式的优化称为Gap优化。最后人们结合了连续增广,分层图,多路增广,Gap优化,主动标号等穷凶极恶的优化,更甚者在此之上狂搞个手动递归,于是产生了增广路算法的高效算法–ISAP算法。

  虽然ISAP算法的理论复杂度仍然不可超越高标预流推进,但其编程复杂度已经简化到发指,如此优化,加上不逊于Dinic的速率(在效率上手工Dinic有时甚至不如递归ISAP),我们没有不选择它的理由。

  因此本文强烈推荐ISAP作为网络流首选算法。

  其实现方法见下文,除去例行的添边操作,不超过50行的代码,何乐而不为之,以下实现仍有优化的余地(在计算初始标号时,为减小代码量直接忽略之,其复杂度不变,但实现后效率有5%左右的下降,如果乐意修正的话可以进行改良,当然不修正不影响算法正确性)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值