网络流初学整合

首先说明,此篇博客是我在初学网络流时将看到过的所有很好很经典博客里简单易懂或者精彩的部分摘取组合而成的。每一篇博客侧重点不同,有些讲的比较粗略但是另一些博客很详细,所以我觉得将这些精华聚合在一起还是很重要的,每篇博客我都会标明出处。另外有些地方我也会增加一些我自己的见解,如果有不对的地方,希望各位大佬指出。如果您觉得好的话欢迎转载,但请说明出处:http://blog.csdn.NET/qq_34374664/article/details/52787481

转自:点击打开链接

必须知识:最短路径问题
   1.Dijkstra
    适用于满足所有权系数大于等于0(lij≥0)的网络最短路问题,能求出起点v1到所有其他点vj的最短距离;
    朴素的Dijkstra算法复杂度为O(N^2),堆实现的Dijkstra复杂度为O(NlogN).


   2.bellman-ford
    适用于有负权系数,但无负回路的有向或无向网络的最短路问题,能求出起点v1到所有其它点 vj的最短距离。bellman-ford算法复杂度为O(V*E)。

   3.Floyed
    适用于有负权系数,可以求出图上任意两点之间的最短路径。DP思想的算法,时间复杂度为O(N^3);
    for ( k= 1; k<= n; k++)
    for ( i= 1; i<= n; i++)
    if (graph[i][k]!=INF)
  for ( j= 1; j<= n; j++)
     if (graph[k][j]!=INF && graph[i][k]+graph[k][j]< graph[i][j])
   graph[i][j]= graph[i][k]+ graph[k][j];
 

NO.1  s-t最大流
 两大类算法
 1.增广路算法
  Ford-Fulkerson算法: 残留网络中寻找增加路径
         STEP0:置初始可行流。
         STEP1:构造原网络的残量网络,在残量网络中找s-t有向路。如果没有,算法得到最大流结束。否则继续下一步。
                STEP2:依据残量网络中的s-t有向路写出对应到原网络中的s-t增广路。对于增广路中的前向弧,置s(e)=u(e)- f(e)。对于反向弧,置s(e)=f(e)                        STEP3:计算crement=min{s(e1),s(e2),…,s(ek)} 
                       STEP4:对于增广路中的前向弧,令f(e)=f(e)+crement;对于其中的反向弧,令f(e)=f(e)-crement,转STEP1。
  关键点:寻找可增广路。决定了算法复杂度。
  实现:Edmonds-Karp  通过采用了广度优先的搜索策略得以使其复杂度达到O(V*E^2)。 
         
  优化—> Dinic算法(*)
  Dinic算法的思想是为了减少增广次数,建立一个辅助网络L,L与原网络G具有相同的节点数,但边上的容量有所不同,在L上进行增广,将增广后的流值回写到原网络上,再建立当前网络的辅助网络,如此反复,达到最大流。分层的目的是降低寻找增广路的代价。
  算法的时间复杂度为O(V^2*E)。其中m为弧的数目,是多项式算法。邻接表表示图,空间复杂度为O(V+E)。


 2.预流推进算法  
  一般性的push-relabel算法: 时间复杂度达到O(V^2*E)。(*) 
  relabel-to-front算法->改进 
  最高标号预流推进:时间复杂度O(V^2*sqrt(E))
 

NO2. 最小费用最大流
  求解最小费用流的步骤和求最大流的步骤几乎完全一致,只是在步骤1时选一条非饱和路时,应选代价和最小的路,即最短路。
  步骤1. 选定一条总的单位费用最小的路,即要给定最小费用的初始可行流,而不是包含边数最小的路。
  步骤2. 不断重复求最大流的步骤来进行,直到没有饱和路存在为止。然后计算每个路的总费用。
  和Edmonds-Karp标号算法几乎一样,因为这两种算法都使用宽度优先搜索来来寻找增广路径,所以复杂度也相同,都是O(V*E^2)。
  
  连续最短路算法 + 线性规划对偶性优化的原始对偶算法(*)
       传说中,没见过,据说复杂度是O(V^3) 
 
NO3. 有上下届的最大流和最小流(通过添加点来进行转化)(*)

NO4. 相关图论算法
 二分图最大匹配: 加s和t构造最大流
  专用算法:hungary算法 O(M*N)

 二分图最佳匹配: 加s和t构造最小费用最大流
  专用算法:KM算法 
    朴素的实现方法,时间复杂度为O(n^4)
    加上松弛函数O(n^3)

 最小路径覆盖:
       顶点数-二分图的最大匹配

 s-t最小边割集:
       最大流最小割定理:最小割等于最大流

 普通最小边割集:
       Stoer-Wagner Minimum Cut O(n^3)
       
 二分图的最大独立集:
   N - 二分图的最大匹配(POJ monthly)girls and boys
          反证法证明
        普通图的最大独立集是np问题。(*)



转自:点击打开链接

引言

  过去听起来高深莫测的网络流算法,现在已飞入寻常百姓家了,对于每一个OIER,网络流是一个神圣的东西(个人见解),但神圣的同时,它并不是那样抽象,最形象的模型就是水流,从长江源点无限的向外流水,而大海(汇点)则在不断地喝水,当然,你也可以不把它想成水,或者是其他一切可以流动的东西。而事实上,有些东西的流动比较流畅,而某些东西可能相对而言比较粘稠,流速更慢,因此,就产生了一个问题,单位时间内的总流量最多多少,这里会根据流速给定单位时间内的流量,这就是最先开启网络流之门的最大流算法,它的解决方式将在后面谈到,再想一下,如果水管是另一个物流公司所有,那么你会根据从哪里运到哪里付出一定的代价, 为了你自己的利润,显然要找一个在运的东西最多的前提下有最小费用的方案,这就引出了下一个问题,最小费用最大流。再引用某牛一句话当然也有有钱没处花的傻子,去求最大费用最大流,而事实上,题目会出现这个模型,为了避免你成为傻瓜,现在你要给它一个新的定义:最大收益流,这时的你,变成了物流公司的经理,而客户的路线由你规划,为了你的钱包,最大收益必不可少。

 

正文

 第一部分.概念性问题(基本定理及定义)

        对于一些网络流新手来说,有必要知道一些基本定义和基本定理,这些虽然看起来理论价值不大,但是现在的许多网络流描述需要这些专业性的词语,所以还是  有些了解为好。

       首先对于图G

       G 的流是一个实值函数 f f (u, v) 表示顶点 u 到顶点 的流,它可以为正, 为零,也可以为负,且满足下列三个性质:

1.容量限制:对所有u, v Î,要求 (u, v) £ c(u, v)  反对称性:对所有u, v Î,要求 (u, v) =- (v, u) 

2.流守恒性:对所有Î-{ s, t} ,要求 å (u, v) = 

3.整个流网络 的流量 = å (s, v)  f= å (u, t) 



接下来定义各种算法中都要用到的一些东东:

1.残留网络

给定一个流网络= (, E) 和流 f,由 压得的 的残留网络Gf= (, E f ) ,定义 c f (u, v) 为残留网络G f  中边 (u, v) 的容量。如果弧 (u, v) Î 或弧 (v, u) Î ,则  (u, v) Î E f ,且 c f (u, v) = c(u, v) - (u, v) 

  残留网络又被称为剩余图。

2.点的高度和层次,这是两个相对的概念,高度的定义为到汇点的最短路径长度,而层次则是指到源点的最短路径长度(这里的路径长度按照各个边的长度都为1算),这两个量是在最大流算法中贯穿始末的利器。

接下来引入最大流最小割定理

 对了,可能有同学还不知道什么是最小割,在这里提一下

 流网络 = (, E) 的割 (,划分成 = - 两部分,使得 Î Î。定义割 (,的容量为 c(,T ),

  对 于 最 小 的 , 它 是 最 小 割 。

3.  最 大 流 最 小 割 定 理

    在 流 网  络 中,最 小 割 的 容 量 等 于 最 大 流 的 流 量 。(证 明 再 次 略 过 )

 

   第二部分.最大流的算法

下面步入与实际问题更加接近的算法实现部分,首先给出问题,给定一个流网络,求源到汇在单位时间内的最大流量。

最简单而效率较好的算法 是基于增广路的算法,这类算法在王欣上大牛的论文中有详细介绍,但我仍然想谈谈我的想法,希望能起到抛砖引玉的作用。基于增广路的算法主要有两种:MPLA,Dinic,SAP.其中最简单的是MPLA,最实用最简洁也是最多人用的是Dinia,SAP的范围也很广,加上GAP优化后的效率也让人咋舌,这也是最近SAP大泛滥的原因吧!个人比较喜欢Dinic,数据变态就用最高标号预流推进,SAP用的比较少,当然,用什么算法还是看你自己的感觉吧。有些人认为增广路算法格式低效,于是想出了对于每个节点操作的算法,这类算法以预留推进为顶梁柱,MPM也勉强归入这一类吧。

 

1.MPLA算法

即最短路径增值算法,可以有一个简单的思想,每次都找一条从源到汇的路径来增广,直到不能增广为止,之中算法的正确性是可以保证的,但效率不尽如人意,有些时候,把事情格式化反而有益,这里的MPLA就是这样,它只在层次图中找增广路,构建出层次图之后,用BFS不断增广,直到当前层次图中不再有增广路,再重新构建层次图,如果汇点不在层次图内,则源汇不再连通,最大流已经求出,否则继续执行增广,如此反复,就可以求出最大流,在程序实现时层次图不用被构建出来,只需要BFS出各点的距离标号,找路径时判断对于f(u,v)是否有d[u]+1=d[v]即可。

如果每建一次层次图成为一个阶段,则在最短路径增值算法中,最多有N个阶段,证明再次略过。

因此在整个算法中,最多有N个阶段,每个阶段构建层次图的BFS时间复杂度为O(m),N次,因此构建层次图的总时间为O(mn),而在增广过程中,每一次增广至少删除一条边,因此增广m次,加上修改流量的时间,每一阶段的增广时间为O(m*(m+n)),共有N个阶段,所以复杂度为O(n*m*(m+n))=O(nm^2),这也是该算法的时间复杂度。

 

2.Dinic算法

MPLA虽然简单,但经常会点超时,我们把增广过程中的BFS改成DFS,效率会有比较大的提高么?答案是肯定的,至此我们已经得到了Dinic的算法流程,只是将MPLA的增广改为DFS,就能写出那美妙的Dinic了,同样,分析一下时间,在DFS过程中,会有前进和后退两种情况,最多前进后退N次,而增广路最多找M次,再加上N个阶段,所以Dinic的复杂度就是O(mn^2),事实上,它也确实比MPLA快很多,简洁而比较高效,这也是许多OIER选择Dinic的理由了吧,毕竟,写它可能会节省出较长时间来完成其他题目.

复制代码
 1  2  program dinic(input,output);
 3  var
 4     f           : array[0..1000,0..1000] of longint;
 5     number      : array[0..1000] of longint;
 6     q           : array[0..10000] of longint;
 7     n,m,ans,s,t : longint;
 8  procedure init;
 9  var
10     x,y,c : longint;
11     i     : longint;
12  begin
13     readln(m,n);
14     s:=1;
15     t:=n;
16     fillchar(f,sizeof(f),0);
17     for i:=1 to m do
18     begin
19        readln(x,y,c);
20        inc(f[x,y],c);
21     end;
22  end; {
     init }
23  function min(aa,bb :longint ):longint;
24  begin
25     if aa<bb then
26        exit(aa);
27     exit(bb);
28  end; {
     min }
29  function bfs(): boolean;
30  var
31     head,tail : longint;
32     now,i     : longint;
33  begin
34     fillchar(number,sizeof(number),0);
35     head:=0;
36     tail:=1;
37     q[1]:=s;
38     number[s]:=1;
39     while head<tail do
40     begin
41        inc(head);
42        now:=q[head];
43        for i:=1 to n do
44       if f[now,i]>0 then
45          if number[i]=0 then
46          begin
47             number[i]:=number[now]+1;
48             inc(tail);
49             q[tail]:=i;
50          end;
51     end;
52     if number[t]=0 then
53        exit(false);
54     exit(true);
55  end; {
     bfs }
56  function dfs(now,flow :longint ):longint;
57  var
58     tmp,i : longint;
59  begin
60     if now=t then
61        exit(flow);
62     for i:=1 to n do
63        if number[i]=number[now]+1 then
64       if f[now,i]>0 then
65       begin
66          tmp:=dfs(i,min(flow,f[now,i]));
67          if tmp<>0 then
68          begin
69             inc(f[i,now],tmp);
70             dec(f[now,i],tmp);
71             exit(tmp);
72          end;
73       end;
74     exit(0);
75  end; {
     dfs }
76  procedure main;
77  var
78     tmp : longint;
79  begin
80     ans:=0;
81     while bfs() do
82     begin
83        tmp:=dfs(s,maxlongint>>2);
84        while tmp<>0 do
85        begin
86       inc(ans,tmp);
87       tmp:=dfs(s,maxlongint>>2);
88        end;
89     end;
90     writeln(ans);
91  end; {
     main }
92  begin
93     init;
94     main;
95  end.
复制代码

 

3.SAP算法

    SAP也是找最短路径来增广的算法,有这样一句话:SAP算法更易理解,实现更简单,效率更高,而也有测试表明,SAP加上重要的GAP优化后,效率仅次于最高标号预流推进算法,因此如果你想背一个模板,SAP是最佳选择。SAP在增光时充分的利用了以前的信息,当按照高度找不到增广路时,它会对节点重新标号,h[i]=min{h[j]}+1(c[i,j]>0),这也是SAP比较核心的思想,而根据这个我们可以发现,当高度出现间隙时,一定不会存在增广路了,算法已经可以结束,因此,这里引入间隙优化(GAP),即出现间隙时结束算法。

    在算法实现中,初始标号可以全部置为0,在增广过程中在逐渐提升高度,时间上可能会有常数的增加,但不改变渐进时间复杂度。同时为了简洁,SAP实现时用递归,代码不过80行左右。

复制代码
 1 View Code 
 2  program sap(input,output);
 3  var
 4     c        : array[0..1000,0..1000] of longint;
 5     h,vh        : array[0..1000] of longint;
 6     flow,n,m,ans    : longint;
 7     tmpflow    : longint;
 8     can        : boolean;
 9  procedure init;
10  var
11     i,j        : longint;
12     xx,yy,cc : longint;
13  begin
14     fillchar(c,sizeof(c),0);
15     fillchar(h,sizeof(h),0);
16     ans:=0;
17     readln(m,n);
18     for i:=1 to m do
19     begin
20        readln(xx,yy,cc);
21        inc(c[xx,yy],cc);
22     end;
23  end; {
    init }
24  procedure dfs(now : longint );
25  var
26     min,tmp : longint;
27     i       : longint;
28  begin
29     min:=n-1;
30     tmp:=tmpflow;
31     if now=n then
32     begin
33        can:=true;
34        inc(ans,tmpflow);
35        exit;
36     end;
37     for i:=1 to n do
38        if c[now,i]>0 then
39        begin
40       if h[i]+1=h[now] then
41       begin
42          if c[now,i]<tmpflow then
43             tmpflow:=c[now,i];
44          dfs(i);
45          if h[1]>=n then
46             exit;
47          if can then
48             break;
49          tmpflow:=tmp;
50       end;
51       if h[i]<min then
52          min:=h[i];
53        end;
54     if not can then
55     begin
56        dec(vh[h[now]]);
57        if vh[h[now]]=0 then
58       h[1]:=n;
59        h[now]:=min+1;
60        inc(vh[h[now]]);
61     end
62     else
63     begin
64        dec(c[now,i],tmpflow);
65        inc(c[i,now],tmpflow);
66     end;
67  end; {
    dfs }
68  begin
69     init;
70     fillchar(vh,sizeof(vh),0);
71     vh[0]:=n;
72     while h[1]<n do
73     begin
74        tmpflow:=maxlongint>>2;;
75        can:=false;
76        dfs(1);
77     end;
78     writeln(ans);
79  end.
复制代码

 

4.MPM算法

    这个算法我还没有实践过,因为它的实现过程比较繁琐,而且时间效率不高,是一个只具有理论价值的算法,这个算法每次都处理单独节点,记每个节点入流和与出流和的最小值作为thoughput(now)(定义在非源汇点),每次先从now向汇推大小为thoughput(now)的流量,在从点now向源点拉大小为thoughput(now)的流量,删除该节点,继续执行直到图中只剩下源汇。时间复杂度为O(n^3),但时间常数较大,时间效率不高。

 

5.预留推进算法

    以上的算法中,基本上都需要从大体上来把握全局,而预留推进算法则是将每一个顶点看作了一个战场,分别对他们进行处理,在处理过程中,存在某些时间不满足流量收支平衡,所以对预先推出的流叫做预流,下面来看算法如何将预流变成最大流的。

    预留推进算法有两个主过程,pushrelabel,即推进和重标号,它是在模拟水流的过程,一开始先让源的出弧全部饱和,之后随着时间的推移,不断改变顶点的高度,而又规定水流仅能从高处流向低处,所以在模拟过程中,最终会有水流入汇,而之前推出的多余的水则流回了源,那么我们每次处理的是什么节点呢?把当前节点内存有水的节点称为活跃节点,每次对活跃节点执行推流操作,直到该节点不再活跃,如果不能再推流而当前节点仍未活跃节点,就需要对它进行重新标号了,标号后再继续推流,如此重复,直到网络中不再存在活跃节点为止,这时源的流出量就是该网络的最大流。注意,对于活跃节点的定义,不包括源汇,否则你会死的很惨。

    朴素的预留推进的效率还过得去,最多进行nm次饱和推进和n^2m次不饱和推进,因此总的时间复杂度为O(mn^2)

    事实上,如同增广路算法引入层次图一样,定下一些规则,可以让预留推进算法有更好的时间效率,下面介绍相对而言比较好实现的FIFO预留推进算法,它用一个队列来保存活跃节点,每次从队首取出一个节点进行推进,对一个节点relabel之后把它加到队尾,如此执行,直到队列为空,这样一来,预留推进算法的时间复杂度降为O(n^3),实现的时候,可以加上同样的间隙优化,但注意,出现间隙时不要马上退出,将新标号的的高度置为n+1,继续执行程序,这样会让所有的剩水流回源,满足流量收支平衡,以便最后的统计工作。

复制代码
 1 View Code 
 2  program preflow(input,output);
 3  var
 4     f,c        : array</
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值