网络流入门讲解.docx
- 文档编号:25064411
- 上传时间:2023-06-04
- 格式:DOCX
- 页数:17
- 大小:26.27KB
网络流入门讲解.docx
《网络流入门讲解.docx》由会员分享,可在线阅读,更多相关《网络流入门讲解.docx(17页珍藏版)》请在冰豆网上搜索。
网络流入门讲解
放假以来把网络流看来几篇,今天终于有点感觉,网上很多资料,我认为网络流最难理解的是加反向边,这个东西纠结了很久,网上搜到一个blog才领悟到一句真理:
“反向边的作用就是给程序一个可以后悔的机会”,详细请看下面的文章,由于自己对网络流还是vegetablesbird,所以只好转一下人家写的好文章。
网络流(NetworkFlow)
将每条有向边想象成传输物质的管道。
每个管道都有一个固定的容量,可以看作是物质能流经该管道的最大速度(譬如可以想象为水流和河槽)。
顶点是管道间的连接点,除了源点(S,Source)和汇点(T,Target)以外,物质只流经这些顶点。
而不聚集在顶点中。
注:
下文提到的数字,基本都可加单位“单位流量”来理解。
一、网络流基础
网络流中的最大流研究的问题是:
在不违背容量限制的条件下,把物质从源点传输到汇点的最大速率是多少?
上边的图示中,还有些需要说明,设G=(V,E)是一个流网络,其容量函数为c(c(u,v),Capicity)。
s为源点,t为汇点。
G的流是一个函数f(Flow)。
上图中每条边标记的是流网络的容量c(u,v),右图中,G中的流|f|=19。
图中只显示正网络流。
如果f(u,v)>0,则标记(u,v)为f(u,v)/c(u,v)(斜杠仅仅用来区分流和容量,不表示相除)。
如果f(u,v)<=0,边(u,v)只标记它的容量。
譬如(s,v1)这条边,最大流量限制是16,但实际流过11,所以表示为11/16。
网络流算法要基于三种思想:
残留网络(ResidualNetwork),增广路径(AugmentingPath)和割(Cut)。
和开头同一个例子,上图,图(b)就是残留网络,仔细观察一下,应该就可以明白,譬如边(s,v1),从s到v1流过11,还剩5,所以残余容量是5,当然,流量是守恒的,所以反向从v1到s流过11。
剩余的容量+反向平衡的流量共同构成了残留网络。
对于名词“残留容量(ResidualCapacity)”的定义:
在不超过容量c(u,v)的条件下,从u到v之间可以压入的额外网络流量,就是(u,v)的残留容量(ResidualCapacity),公式定义是:
cf(u,v)=c(u,v)–f(u,v)。
而残留网络Gf =(V,Ef)。
图(b),阴影覆盖的边为增广路径 p,其残留容量为cf(p)=cf(v2,v3)=4。
看图应该就可以大概理解什么是增广路了。
看图可以发现s到v2还可以通过5,v2到v3还可以通过4,v3到t还可以流5,按照常识,也可得出,这条路径还可以再流过4的结论。
而从s输送4到t的这条路就是增广路。
"增广路径p”的定义:
p为残留网络Gf中从s到t的一条简单路径。
在不违反边的容量限制条件下,增广路径上的每条边(u,v)可以容纳从u到v的某额外正网络流。
Starvae师兄在讲解增广路时,有一段通俗的描述:
假如有这么一条路,这条路从源点开始一直一段一段的连到了汇点,并且,这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。
那么,我们一定能找到这条路上的每一段的(容量-流量)的值当中的最小值delta。
我们把这条路上每一段的流量都加上这个delta,一定可以保证这个流依然是可行流。
这样我们就得到了一个更大的流,他的流量是之前的流量+delta,而这条路就叫做增广路。
另外,这里得出的,计算增广路流量的公式非常重要:
cf(p)=min{cf(u,v):
(u,v)在p上},因为最大流算法的核心基本也就是寻找增广路了。
最大流最小割定理:
一个流是最大流,当且仅当它的残留网络不包含增广路径。
流网络的割(S,T)将V划分为S和T=V–S两部分,使得s∈S,t∈T。
如果f是一个流,则穿过割(S,T)的净流被定义为f(S,T)。
割(S,T)的容量为c(S,T)。
一个网络的最小割就是网络中所有割中最小容量的割。
上图中,流网络的割(S={s,v1,v2},T={v3,v4,t}),S中的顶点是黑色,T中的顶点是白色。
穿越(S,T)的净流量为:
f(v1,v3)+f(v2,v3)+f(v2,v4)=12+(-4)+11=19;容量为:
c(v1,v3)+c(v2,v4)=12+14=26。
可以看出,割的净流由双向的网络流组成。
而割的容量仅由S到T的边计算而得。
还有一个很有重要的知识:
反向边。
如图(a),1是源点,4是汇点。
很明显如果第一次迭代找到的增广路是1→2→4和1→3→4,则最大流是2,但是如果第一次迭代找到的增广路是1→2→3→4,那么流量只有1,如图(b)是残留网络,这时候,反向边就起作用了,由于反向边的原因,第二次迭代的时候,又找到一条增广路1→2→3→4。
这样下来,总的流量还是2。
还是Starvae师兄的解释:
当我们第二次的增广路走3→2这条反向边的时候,就相当于把2→3这条正向边已经是用了的流量给“退”了回去,不走2→3这条路,而改走从2点出发的其他的路也就是2→4。
(有人问如果这里没有2→4怎么办,这时假如没有2→4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3→4上的流量由1→3→4这条路来“接管”。
而最终2→3这条路正向流量1,反向流量1,等于没有流量。
反向边的作用就是给程序一个可以后悔的机会。
——一语中的啊。
(这句话我加的^^)
二、Ford-Fulkerson算法
Ford-Fulkerson算法在实际中并不常用,但是它提供了一种思想:
先找到一条从源点到汇点的增广路径,这条路径的容量是其中容量最小的边的容量。
然后,通过不断找增广路,一步步扩大流量,当找不到增广路时,就得到最大流了(最大流最小割定理)。
可以看看Ford-Fulkerson算法的伪代码,
FORD-FULKERSON(G,s,t)
1 foreachedge(u,v)∈E[G]
2 dof[u,v]←0
3 f[v,u]←0
4 whilethereexistsapathpfromstotintheresidualnetworkGf
5 docf(p)←min{cf(u,v):
(u,v)isinp}
6 foreachedge(u,v)inp
7 dof[u,v]←f[u,v]+cf(p)
8 f[v,u]←-f[u,v]
从伪代码可以看出,Ford-Fulkerson的核心过程就是:
第4~8行的while循环反复找出Gf 中的增广路径p,并把沿p的流f加上其残留容量cf(p)。
当不再有增广路径时,流f就是一个最大流。
完整图示:
(f)最后在while循环测试的残留网络,它没有增光路径,所以(e)中显示的流就是最大流。
三、压入重标记push_relabel算法O(VE)
Push-relabel用到一个很有趣的概念一Preflow(前置流)他允许流进的量比流出的量还要多,有水来就先流进来,流不出去再说。
Push-relabel算法的流程如下:
1)我们先假定一个高度函数h(u),他代表u点的高度,只有
h(u)比较高的点才能够将水流到h(u)比较低的点;
2)在程序一开始的时候,让sourcenode的高度是n(node数), 其它点的高度都是0,这样sourcenode才有足够的高度可以流往其它地方;
3)然后,让sourcenode往其它所有跟他直接相邻的node ,都流水管宽度的水量(流过去之后当然要计算剩下的网络情形,即计算 residual edges(残留网络)
4)对所有activenode(目前有水的 node )做 relabel(重标记)的动作:
在当某个node明明有水,但是他所连出去的所有对象的h(u)都比他还高,则让他的h(u)增加为至少有一条水管可以流出去的量,也就是让这个有水的 activenode的高度变成比他连往的”高度最小的node”+l,(流过去之后还是要计算剩下的网络情形
5)对所有可以做push(压入)动作的node做push的动作。
所谓的Push动作是指:
当某个node有水,并且他有可以流出去的边,且他刚好比可以流出去的那个点高度高一点点(高度恰好比他高1),那就把某个node的水流过去,要流多少呢?
以下两者取min。
流出去的水管的量(也就是说,这个activenode的水量很多, 足够把这条水管塞满(饱和),这个时候就叫做saturatingPush)(饱和压入)某个node现在的水量(这个node的水量不足以把流出去的这个水管填满,称作nonsaturatingPush(不饱和压入)
6)重复Relabel和Push的工作,一直到没有activenode为止,此时从sourcenode所流出的总流量(Preflow),就是这个图的最大流量。
Push-relabel algorithm 提供了最大流另一方向的思考,且就效率而言,Push-Relabel的复杂度为o(vE )
这里以POJ1459为例
[cpp] viewplaincopy
1.#define MIN INT_MIN
2.#define MAX INT_MAX
3.#define N 110
4.int min(int a,int b){return a>b?
b:
a;}
5.int c[N][N];//残留容量
6.int ef[N];//顶点余流
7.int h[N];//顶点高度
8.int n;
9.int push_relabel(int s,int t){
10. int i,j;
11. int ans = 0;
12. memset(h,0,sizeof(h));
13. h[s] = t+1;//源点初始高度
14. memset(ef,0,sizeof(ef));
15. ef[s] = MAX;//源点初始余流
16. queue
17. qq.push(s);
18. while(!
qq.empty()){
19. int u = qq.front();
20. qq.pop();
21. for(i=0;i<=t;i++){
22. int p;
23. int v = i;
24. if(c[u][v] 25. else p = ef[u]; 26. if(p>0 && (u==s || h[u] == h[v] +1)){ 27. c[u][v] -= p; 28. c[v][u] += p; 29. if(v==t)ans+=p;//如果到达了汇点,就将流值加入到最大流中 30. ef[u] -= p; 31. ef[v] += p; 32. if(v! =s && v! =t)qq.push(v);//只有既不是源点也不是汇点才进队 33. } 34. } 35. //如果不是源点且仍有余流,则重标记高度再进队。 36. //这里只是简单的将高度增加了一个单位,也可以像上面所说的一样赋值为最低的相邻顶点的高度高一个单位 37. if(u! = s && u! =t && ef[u]>0) { 38. h[u]++; 39. qq.push(u); 40. } 41. } 42. return ans; 43.} 44.int main(){ 45. int np,nc,m; 46. while(scanf("%d%d%d%d",&n,&np,&nc,&m) ! = -1){ 47. int s = n,t = n+1; 48. int i,j; 49. memset(c,0,sizeof(c)); 50. char ss[30]; 51. for(i=0;i 52. int u,v,w; 53. scanf("%s",ss); 54. sscanf(ss,"(%d,%d)%d",&u,&v,&w); 55. c[u][v] += w; 56. } 57. for(i=0;i 58. int u,w; 59. scanf("%s",ss); 60. sscanf(ss,"(%d)%d",&u,&w); 61. c[s][u] += w; 62. } 63. for(i=0;i 64. int v,w; 65. scanf("%s",ss); 66. sscanf(ss,"(%d)%d",&v,&w); 67. c[v][t] += w; 68. } 69. printf("%d\n",push_relabel(s,t)); 70. } 71. return 0; 72.} 四、Edmonds-Karp(EK)算法 O(V*E*E) EK算法基于Ford-Fulkerson算法,唯一的区别是将第4行用BFS(广度优先搜索)来实现对增广路径p的计算。 EK算法伪代码基本和上边的Ford-Fulkerson算法一样。 类似用DFS实现的还有Dinic算法。 它们都属于SAP(ShortestAugmentingPath)算法,从英文即可看出,它们每次都在寻找最短增广路。 对于EK算法,每次用一遍BFS寻找从源点s到终点t的最短路作为增广路径,然后增广流量f并修改残量网络,直到不存在新的增广路径。 E-K算法的时间复杂度为O(VE^2),适用于稀疏边,由于BFS要搜索全部小于最短距离的分支路径之后才能找到终点,因此频繁的BFS效率是比较低的。 实践中此算法使用的机会较少。 这里以POJ1273为例,这里可以作为EK模板,也是我的第一道网络流,mark一下 [cpp] viewplaincopy 1.#define MIN INT_MIN 2.#define MAX INT_MAX 3.#define N 204 4. 5.int c[N][N];//边容量 6.int f[N][N];//边实际流量 7.int pre[N];//记录增广路径 8.int res[N];//残余网络 9.queue 10.void init(){ 11. while(! qq.empty())qq.pop(); 12. memset(c,0,sizeof(c)); 13. memset(f,0,sizeof(f)); 14.} 15.int EK(int s,int t){ 16. int i,j; 17. int ans=0; 18. while (1){ 19. memset(res,0,sizeof(res)); 20. res[s] = MAX;//源点的残留网络要置为无限大! 否则下面找增广路出错 21. pre[s] = -1; 22. qq.push(s); 23. //bfs找增广路径 24. while(! qq.empty()){ 25. int x = qq.front(); 26. qq.pop(); 27. for(i=1;i<=t;i++){ 28. if(! res[i] && f[x][i] < c[x][i]){ 29. qq.push(i); 30. pre[i] = x; 31. res[i] = min(c[x][i] - f[x][i], res[x]);//这里类似dp,如果有增广路,那么res[t]就是增广路的最小权 32. } 33. } 34. } 35. if(res[t]==0)break;//找不到增广路就退出 36. int k = t; 37. while(pre[k]! =-1){ 38. f[pre[k]][k] += res[t];//正向边加上新的流量 39. f[k][pre[k]] -= res[t];//反向边要减去新的流量,反向边的作用是给程序一个后悔的机会 40. k = pre[k]; 41. } 42. ans += res[t]; 43. } 44. return ans; 45.} 46.int main(){ 47. int n,m; 48. while(scanf("%d%d",&n,&m) ! = -1){ 49. int i,j; 50. init(); 51. while(n--){ 52. int a,b,v; 53. scanf("%d%d%d",&a,&b,&v); 54. c[a][b]+=v; 55. } 56. printf("%d\n",EK(1,m)); 57. } 58. return 0; 59.} 四、ImprovedSAP(ISAP)算法 ISAP字面意思是改良的最短增广路算法。 关于ISAP,一位叫DD_engi的神牛讲非常清楚,引用一下: SAP算法(bydd_engi): 求最大流有一种经典的算法,就是每次找增广路时用BFS找,保证找到的增广路是弧数最少的,也就是所谓的Edmonds-Karp算法。 可以证明的是在使用最短路增广时增广过程不超过V*E次,每次BFS的时间都是O(E),所以Edmonds-Karp的时间复杂度就是O(V*E^2)。 如果能让每次寻找增广路时的时间复杂度降下来,那么就能提高算法效率了,使用距离标号的最短增广路算法就是这样的。 所谓距离标号,就是某个点到汇点的最少的弧的数量(另外一种距离标号是从源点到该点的最少的弧的数量,本质上没什么区别)。 设点i的标号为D[i],那么如果将满足D[i]=D[j]+1的弧(i,j))叫做允许弧,且增广时只走允许弧,那么就可以达到“怎么走都是最短路”的效果。 每个点的初始标号可以在一开始用一次从汇点沿所有反向边的BFS求出,实践中可以初始设全部点的距离标号为0,问题就是如何在增广过程中维护这个距离标号。 维护距离标号的方法是这样的: 当找增广路过程中发现某点出发没有允许弧时,将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最小值加一。 这种维护距离标号的方法的正确性我就不证了。 由于距离标号的存在,由于“怎么走都是最短路”,所以就可以采用DFS找增广路,用一个栈保存当前路径的弧即可。 当某个点的距离标号被改变时,栈中指向它的那条弧肯定已经不是允许弧了,所以就让它出栈,并继续用栈顶的弧的端点增广。 为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”: 初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧,还有一种在常数上有所优化的写法是改变距离标号时把当前弧设为那条提供了最小标号的弧。 当前弧的写法之所以正确就在于任何时候我们都能保证在邻接表中当前弧的前面肯定不存在允许弧。 还有一个常数优化是在每次找到路径并增广完毕之后不要将路径中所有的顶点退栈,而是只将瓶颈边以及之后的边退栈,这是借鉴了Dinic算法的思想。 注意任何时候待增广的“当前点”都应该是栈顶的点的终点。 这的确只是一个常数优化,由于当前边结构的存在,我们肯定可以在O(n)的时间内复原路径中瓶颈边之前的所有边。 优化: 1.邻接表优化: 如果顶点多的话,往往N^2存不下,这时候就要存边: 存每条边的出发点,终止点和价值,然后排序一下,再记录每个出发点的位置。 以后要调用从出发点出发的边时候,只需要从记录的位置开始找即可(其实可以用链表)。 优点是时间加快空间节省,缺点是编程复杂度将变大,所以在题目允许的情况下,建议使用邻接矩阵。 2.GAP优化: 如果一次重标号时,出现距离断层,则可以证明ST无可行流,此时则可以直接退出算法。 3.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 网络 流入 讲解