能力强的人善于解决问题,有智慧的人善于绕过问题。 区别很微妙,小心谨慎做后者。
全部博文(399)
分类: LINUX
2010-02-23 18:49:05
网络流的最短增广路算法
在网络流问题中,最常见的方法是Ford-Fulkerson方法。这种方法每次找出一条残留网络中的增广路径并进行增广。整个算法运行过程满足流的三个
性质(容量限制、反对称性、流守恒性)。与此相对的预流推进算法不满足流守恒性。在Ford-Fulkerson方法中,如何寻找增广路径是影响算法效率
的主要因素。一种好的思想是每次寻找长度最短的路径。按照这种思想,如果采用BFS寻找增广路径,时间复杂度为O(n*m^2),效率不能令人满意。本文
介绍一种最短增广路算法(Shortest Augmenting Path
Algorithm),它的复杂度为O(n^2*m)。在实践中,这种算法效率还是比较高的。另外,著名的DINIC算法也与此算法有着密切联系。
在SAP算法中,我们定义每个顶点的距离标号(Distance Labels),即残留网络中这个点到汇点的距离。然后,我们只在距离标号相邻的点间寻找增广路径。如果从一个点出发没有容许边,就对重标记并回溯(类似预流推进)。首先介绍一些定义:
残留容量:容量-流量。即,残留容量r[i,j]=c[i,j]-f[i,j]。如果只求流值,则在网络流算法中只需记录残量,而不必记录容量和流量。
残留边、残留网络:有残留容量的边成为残留边,由网络的点集、残留边集构成残留网络。
距离标号 :对于每个点i,定义其在当前残留网络中到汇点的距离为d[i]。如d[t]=0。
容许边:当且仅当r[i,j]>0且d[i]=d[j]+1时,边(i,j)是容许边。我们的算法只考虑容许边。
算法的一开始,我们从汇点进行一次BFS求出距离标号。然后从源点开始递归操作。对于一个点i的操作,如果存在容许边(i,j),当j是汇点时增广 并从源点开始递归,否则就令i是j的前驱,并递归j。否则对i重标号,使得d[i]=min{d[j]}+1 ( r[i,j] )>0,并回溯。当d[s]>=n时算法停止,此时不存在任何一条增广路。为提高效率可以改递归为迭代。可以参考一下《Network Flows: Theory, Algorithms, and Applications》中的伪代码:
algorithm shortest augmenting path;
begin
x:=0;
obtain the exact distance labels d(i);
i:=s;
while d(s)begin
if i has an admissible arc then
begin
advance(i);
if i=t then augment and set i=s
end
else retreat(i)
end;
end;
procedure advance(i);
begin
let (i,j) be an admissible arc in A(i);
pred(j):=i and i:=j;
end;
procedure retreat(i);
begin
d(i):=min{d(j)+1: (i,j)∈A(i) and r[i,j]>0};
if i<>s then i:=pred(i);
end;
procedure augment;
begin
using the predecessor indices identify an augmenting path P from the source to the sink;
δ:=min{r[i,j] : (i,j)∈P}
augment δ units of flow along path P;
end;
同样类似预流推进,我们有一个重要优化:间隙优化。即我们定义numb(k)是距离标号为k的点的个数。在重标记点i时,我们检查 numb(d[i])。若numb(d[i])减为0,算法马上结束。我们很容易发现,点i被重标记则其距离标号必然小于n。而此时不存在此标号的顶点, 则容许网络中必然不存在一条增广路径,因此算法结束。这个优化使得算法效率显著提高。
容易证明,SAP的复杂度为O(n^2*m)。而如果采用动态树,复杂度降为O(nmlogn)。只是动态树太过复杂。
提一下DINIC算法:
DINIC算法与SAP十分相似。只是,SAP只是开始进行一次BFS,每次只对单个顶点重标号。而DINIC不对单个顶点重标号,而是多次BFS计算标号。我们套用SAP算法并稍微修改:在过程retreat(i)中,不改变d[i],而是标记i为阻塞的。而容许边(i,j)的定义,其中j必须未阻塞。当源点阻塞,此时形成阻塞流,我们就进行一次反向BFS求得距离标号。由于每次BFS源点的距离标号单调递增,当它大于等于n时算法结束。