1. 首页 > 金融百科

最小费用最大流问题

相关定义

前向弧和后向弧

  在网络D(V, A) 中, 如果对连接发点vs和收点vt 的一条链P, 方向规定为从vs 到vt, 则当链P 中弧( vi,vj)   的方向与规定的方向一致时, 称弧( vi, vj) 为前向弧, 否则称为后向弧。不在这条链上的弧, 不定义前向弧和后向弧。

可扩充链

  设{fij}为一可行流(假设为非负值), 如果存在从发点vs 到收点vt 的链P, 在链P 上, 下列两条同时满足, 则称P 为可扩充链:   ①对于P 上的前向弧( vi,vj) 有fij<cij。   ②对于P 上的后向弧( vi,vj) 有fij>0 。

可扩充链P的费用

  设对于可行流f 存在可扩充链P, 当以ε=1 调整f 而得到可行流f' 时, 两流的费用之差成为可扩充链p 的费用。其中P+和P- 分别表示p 上的前向弧和后向弧。 解决方法  解决最小费用最大流问题,一般有两条途径。一条途径是先用最大流算法算出最大流,然后根据边费用,检查是否有可能在流量平衡的前提下通过调整边流量,使总费用得以减少?只要有这个可能,就进行这样的调整。调整后,得到一个新的最大流。   然后,在这个新流的基础上继续检查,调整。这样迭代下去,直至无调整可能,便得到最小费用最大流。这一思路的特点是保持问题的可行性(始终保持最大流),向最优推进。另一条解决途径和前面介绍的最大流算法思路相类似,一般首先给出零流作为初始流。这个流的费用为零,当然是最小费用的。然后寻找一条源点至汇点的增流链,但要求这条增流链必须是所有增流链中费用最小的一条。如果能找出增流链,则在增流链上增流,得出新流。将这个流做为初始流看待,继续寻找增流链增流。这样迭代下去,直至找不出增流链,这时的流即为最小费用最大流。这一算法思路的特点是保持解的最优性(每次得到的新流都是费用最小的流),而逐渐向可行解靠近(直至最大流时才是一个可行解)。   由于第二种算法和已介绍的最大流算法接近,且算法中寻找最小费用增流链,可以转化为一个寻求源点至汇点的最短路径问题,所以这里介绍这一算法。   在这一算法中,为了寻求最小费用的增流链,对每一当前流,需建立伴随这一网络流的增流网络。例如图 1 网络G 是具有最小 费用的流,边旁参数为c(e) , f(e) , w(e),而图 2 即为该网络流 的增流网络G′。增流网络的顶点和原网络相同。 按以下原则建 立增流网络的边:若G中边(u,v)流量未饱,即f(u,v) < e(u,v),则G ' 中建边(u,v),赋权w ' (u,v)=w(u,v);若G中边(u, v)已有流量,即f(u,v)〉0,则G′中建边(v,u),赋权w′(v,u) =-w(u,v)。建立增流网络后,即可在此网络上求源点至汇点的最短路径,以此决定增流路径,然后在原网络上循此路径增流。这里,运用的仍然是最大流算法的增流原理,唯必须选定最小费用的增流链增流。   计算中有一个问题需要解决。这就是增流网络G ′中有负权边,因而不能直接应用标号法来寻找x至y的最短路径,采用其它计算有负权边的网络最短路径的方法来寻找x至y的最短路径,将 大大降低计算效率。为了仍然采用标号法计算最短路径,在每次建立增流网络求得最短路径后,可将网络G的权w(e)做一次修正,使再建的增流网络不会出现负权边,并保证最短路径不至于因此而改变。下面介绍这种修改方法。 当流值为零,第一次建增流网络求最短路径时,因无负权边,当然可以采用标号法进行计算。为了使以后建立增流网络时不出现负权边,采取的办法是将 G中有流边(f(e)>0)的权w(e)修正为0。为此, 每次在增流网络上求得最短路径后,以下式计算G中新的边权w " (u,v):   w " (u,v)=L(u)-L(v)+w(u,v) (*)   式中 L(u),L(v) -- 计算G′的x至y最短路径时u和v的标号值。第一次求最短径时如果(u,v)是增流路径上的边, 则据最短 路径算法一定有 L(v)=L(u)+w ' (u,v)=L(u)+w(u,v), 代入(*)式必有   w″(u,v)=0。   如果(u,v)不是增流路径上的边,则一定有:   L(v)≤L(u)+w(u,v), 代入(*)式则有 w(u,v)≥0。   可见第一次修正w(e)后,对任一边,皆有w(e)≥0, 且有流 的边(增流链上的边),一定有w(e)=0。以后每次迭代计算,若 f(u,v)>0,增流网络需建立(v,u)边,边权数w ' (v,u)=-w(u,v) =0,即不会再出现负权边。 此外,每次迭代计算用(*)式修正一切w(e), 不难证明对每一条x至y的路径而言,其路径长度都同样增加L(x)-L(y)。因此,x至y的最短路径不会因对w(e)的修正而发生变化。   【计算步骤】   1. 对网络G=[V,E,C,W],给出流值为零的初始流。   2. 作伴随这个流的增流网络G′=[V′,E′,W′]。 G′的顶点同G:V′=V。 若G中f(u,v)<c(u,v),则G′中建边(u,v),w(u,v)=w(u,v)。 若G中f(u,v)>0,则G′中建边(v,u),w′(v,u)=-w(u,v)。   3. 若G′不存在x至y的路径,则G的流即为最小费用最大流, 停止计算;否则用标号法找出x至y的最短路径P。   4. 根据P,在G上增流: 对P的每条边(u,v),若G存在(u,v),则(u,v)增流;若G存在(v,u),则(v,u)减流。增(减)流后,应保证对任一边有c(e)≥ f(e)≥0。   5. 根据计算最短路径时的各顶点的标号值L(v),按下式修 改G一切边的权数w(e):   L(u)-L(v)+w(e)→w(e)。    6. 将新流视为初始流,转2。算法举例

augment path

  直译为“增广路”,其思想大致如下:   原有网络为G,设有一辅助图G',其定义为V(G') = V(G),E(G')初始值(也就是容量)与E(G)相同。每次操作时从Source点搜索出一条到Sink点的路径,然后将该路径上所有的容量减去该路径上容量的最小值,然后对路径上每一条边<u,v>添加或扩大反方向的容量,大小就是刚才减去的容量。一直到没有路为止。此时辅助图上的正向流就是最大流。   我们很容易觉得这个算法会陷入死循环,但事实上不是这样的。我们只需要注意到每次网络中由Source到Sink的流都增加了,若容量都是整数,则这个算法必然会结束。   寻找通路的时候可以用DFS,BFS最短路等算法。就这两者来说,BFS要比DFS快得多,但是编码量也会相应上一个数量级。   增广路方法可以解决最大流问题,然而它有一个不可避免的缺陷,就是在极端情况下每次只能将流扩大1(假设容量、流为整数),这样会造成性能上的很大问题,解决这个问题有一个复杂得多的算法,就是预推进算法。

push label

  直译为“预推进”算法。

Push-Relabel

  直译为压入与重标记算法   除了用各种方法在剩余网络中不断找增广路(augmenting)的Ford-Fulkerson系的算法外,还有一种求最大流的算法被称为压入与重标记(Push-Relabel)算法。它的基本操作有:压入,作用于一条边,将边的始点的预流尽可能多的压向终点;重标记,作用于一个点,将它的高度(也就是label)设为所有邻接点的高度的最小值加一。Push-Relabel系的算法普遍要比Ford-Fulkerson系的算法快,但是缺点是相对难以理解。   Relabel-to-Front使用一个链表保存溢出顶点,用Discharge操作不断使溢出顶点不再溢出。Discharge的操作过程是:若找不到可被压入的临边,则重标记,否则对临边压入,直至点不再溢出。算法的主过程是:首先将源点出发的所有边充满,然后将除源和汇外的所有顶点保存在一个链表里,从链表头开始进行Discharge,如果完成后顶点的高度有所增加,则将这个顶点置于链表的头部,对下一个顶点开始Discharge。   Relabel-to-Front算法的时间复杂度是O(V^3),还有一个叫Highest Label Preflow Push的算法复杂度据说是O(V^2*E^0.5)。我研究了一下HLPP,感觉它和Relabel-to-Front本质上没有区别,因为Relabel-to-Front每次前移的都是高度最高的顶点,所以也相当于每次选择最高的标号进行更新。还有一个感觉也会很好实现的算法是使用队列维护溢出顶点,每次对pop出来的顶点discharge,出现了新的溢出顶点时入队。   Push-Relabel类的算法有一个名为gap heuristic的优化,就是当存在一个整数0<k<V,没有任何顶点满足h[v]=k时,对所有h[v]>k的顶点v做更新,若它小于V+1就置为V+1。

c++程序举例

  #include<cstdio>   const long maxn=1000+10;   const long maxm=440000+100;   const long maxnum=100000000;   long n,m,s=0,tot=0,list[maxn],p[maxn],g[maxn],gg[maxn],dis[maxn];   bool mark[maxn];   struct node   {   long k,f,c,next,g;   }d[maxm<<1];   void insert(long u,long v,long c)   {   d[tot].k=v;   d[tot].f=0;   d[tot].c=c;   d[tot].next=g;   g=tot;   d[tot].g=tot+1;   tot++;   d[tot].k=u;   d[tot].f=0;   d[tot].c=0;   d[tot].next=g[v];   g[v]=tot;   d[tot].g=tot-1;   tot++;   }   void bfs()   {   long x,u,v,h,t;   for (long i=0;i<n;i++)   dis=0;   h=0;   t=1;   list[1]=0;

本文来源于网友自行发布,不代表本站立场,转载联系作者并注明出处