ISAP网络流算法

  ISAP全称Improved Shortest Augmenting Path,意指在SAP算法进行优化。SAP即Edmonds-Karp算法,其具体思路是通过不断向残存网络推送流量来计算整个网络的最大流。阅读本文要求掌握网络流的基础概念,不懂的出门左拐算法导论。ISAP的时间复杂度与EK算法一致,而EK算法的时间复杂度为min(O(E|f|),O(VE^2)),其中O(E|f|)部分是因为其是在FORD-FULKERSON算法上的改进。EK算法在FF算法的基础上将随意取增广路径替换为取最短增广路径,而ISAP在EK算法的基础上剔除了除了第一次外的后续广度优先搜索寻找最短路径的部分。下面对ISAP算法进行说明。

  先说明一些名词,称残存网络中容量非0的边为有效边。

  我们首先在EK算法的基础上,为每个顶点添加新的属性d,表示其到汇点t的最短路径(路径只能包含有效边),这个最短路径是基于残存网络的,而非原图,因此d属性会随着流的推送导致残存网络的变更而变更。显然要维护这样一个属性d,每次在推送流后,都需要基于新形成的残存网络重新执行广度优先搜索算法,以计算每个顶点正确的d属性,这样不就与EK算法完全一样了吗?是的,因此为了避免每次流推送都必须重新计算最短路径,我们需要修改d的定义,d表示顶点到t的距离的某个下界。为了说明这样定义之后就不用再每次重新执行广度优先搜索算法计算d,只需要说明每次向残存网络推送流,只会导致每个顶点到t的距离非严格递增:

  证明:假设我们向图G沿最短路径推送流,并形成新图G'。我们记R(a,b)表示原图中从点a到点b的最短距离,记R'(a,b)表示在新图(残存网络)中a到b的最短距离。我们所要证明的就是对于任意点x都有R(x,t)<=R'(x,t)。假设存在点x,满足R(x,t)>R'(x,t),显然x到t的最短路径之中必然包含由于流推送而新增的边,假设(v,u)是图G'中x到t中距离点x最近的新增边,而(u,v)是处于G中最短增广路径上。这样我们就可以把G'中的x到t的最短路径写作x,...,v,u,...,t,显然R'(x,v)=R(x,v),因为其中不含新增边和删除边(即与G上的最短增广路径无交集)。由于R'(x,t)<R(x,t)可得R'(v,t)<R(v,t),从而有R'(u,t)<R(u,t),因此由前面的说明可知存在增广路径的边(q,w),使得(w,q)存在于R'(u,t)代表的最短路径中。利用同样的思路证明下去我们可以得到无穷多的增广路径上的边出现在了R'(x,t)对应的路径之中,而由于一条最短路径是不可能包含重复边,且所有不同边的总数最多只有2*|E|条(正向边以及逆向边),因此这与R'(x,t)是最短路径这一性质相悖,故假设不成立,因此R'(x,t)>=R(x,t)。

  继续说明流程的执行。由于d是描述一个顶点距离t的下界,因此当一个顶点的属性d超过|V|时,这个顶点将永远无法推送流到t(最短路径不含相同顶点的简单应用)。而我们可以以源点s的属性d是否超过|V|来作为结束推送的条件。具体流程是我们循环推送,每次都从s开始向其余所有与s相邻且d属性为s.d-1的顶点推送无穷的流。我们期望这样的流推送成功当且仅当增广路径上的顶点序列的d属性为s.d,s.d-1,...,1,0,否则推送就不应该成功。显然在推送成功的情况下,由于d是距离下界,因此推送路径一定是最短的增广路径。而所谓的推送失败是指抵达一个顶点x(非t),所有邻接的可抵达顶点(指存在有效边连接)的距离都不等于x-1,这意味着附近的所有顶点到t的距离都由于前面对流的推送而增大了,因此我们也应该对x的距离进行增大(因为一个顶点距离t的最短距离应该是其与可以通过有效边连接的所有顶点到t的最短距离的最小值+1),由于周边的所有顶点的d属性均>=x.d,因此我们将x.d赋值为x.d+1也是合适的,不会违背我们对d的定义,在我们修改了d的值之后,显然这与我们期望的可推送的增广路径已经不相符了,因为d属性为x.d+1的顶点出现了两次,因此我们宣告失败回到之前的顶点。而当我们推送成功且x是方才推送的增广路径上的某个顶点,且此时从前面顶点传来的流还足够支持下次推送,那么我们可以继续寻找其满足d属性为x.d-1的后继顶点,继续推送(这也是ISAP的优化之一)。

  先说明程序会正确停止。由于推送成功就意味着存在增广路径(而且是符合严苛条件的最短增广路径),此时显然还可以继续通过向增广路径推送流来获得最大流。当我们发现不存在增广路径时,即s向周边推送失败,我们会不断增大s.d属性,而当s.d>|V|时,我们就结束推送,此时程序正确结束。

  时间复杂度与EK基本一致,只是将其中原本每次成功推送后执行广度优先搜索去掉,取而代之的是增加了推送失败情况下对顶点d属性的增大。由于一个顶点d属性不会超过|V|,因此我们可以保证每个顶点d属性最多增大|V|次,而总共有|V|个顶点,因此失败处理次数不会超过|V|^2。同时由于我们每次推送成功时也会遍历大量的不同边和不同顶点,这个流程在最糟糕的情况下的时间复杂度为O(|V|+|E|),与广度优先搜索算法一致,因此算法上界应该为O(VE^2+V^2)=O(VE^2),与EK算法一致。

  ISAP有一个通用的GAP优化就是我们将拥有不同d属性值的顶点进行分组,以d属性值作为编号。当我们修正一个顶点p的d属性值(推送失败时),我们可以通过判断p.d这一组元素是否只有一个,当只有一个时,意味着我们再修改p.d为p.d+1后,p.d这一组将会为空,这是一个好消息,因为后面所有从s发出的推送请求都会由于没有p.d组的顶点可以接受请求而失败,因此我们可以断定此时残存网络中的流即是最大流,我们可以通过提前将s.d修改为|V|来跳过后面一系列的失败处理,从而优化性能。

  下面给出代码:

  

bfs(t) //从t开始进行广度优先搜索,如果顶点v到t无路径,则v.d=-1
  for v in V
    v.d=-1    t.d
= 0 que = empty-queue que.enque(t) while(!que.isEmpty()) head = que.deque() group[head.d]+=1 for edge in head.edgeList if(edge.isNegative)//只允许沿着有效边的反向边移动 if(edge.dst.d == -1) edge.dst.d = head.d + 1 que.enque(edge.dst) sendFlow(node, flowLimit) //通过顶点node向周围最多推送flowLimit单位的流,如果推送量为0,则表示推送失败 total = 0 for edge in node.edgeList if(edge.capacity > edge.flow && edge.dst.d == node.d - 1) //发现符合条件的有效边 actually = sendFlow(edge.dst, min(flowLimit, edge.capacity - edge.flow) flowLimit -= actually
       edge.sendFlow(actually); total
+= actually
       if(flowLimit == 0)
         break
if(total == 0) //如果发送失败 group[node.d]-=1 if(group[node.d] == 0) //发现断层 s.d = |V| node.d+=1 group[node.d]+=1 return total isap(s, t) bfs(t) if(s.d == -1)//如果从s到t不存在路径 return 0 sum = 0 while(s.d < |V|) sum += sendFlow(s, INF)
   return sum

 

posted @ 2017-12-01 23:20  cccwiseee  阅读(2525)  评论(0编辑  收藏  举报