文章目录
1.什么是网络流
网络流(英语:Network flow)是指在一个每条边都有容量(Capacity)的有向图分配流,使一条边的流量不会超过它的容量。通常在运筹学中,有向图称为网络。顶点称为节点(Node)而边称为弧(Arc)。
以上内容来自维基百科
1.1网络流的相关定义
- 源点:只进不出的顶点叫做源点。
- 汇点:只出不进的顶点叫做汇点。
- 容量和流量:每条有向边上有两个量,容量和流量。
1.2相关性质
- 容量限制:对所有顶点对u,v∈V,满足f(u, v) ≦ c(u, v);
- 反对称性:对所有顶点对u,v∈V,满足f(u, v) = - f(v, u);
- 流守恒性:对所有顶点对u∈V-{s, t},满足Σv∈Vf(u,v)=0。
这里看不懂也没关系,可以先看一下下面具体思路
1.3求最大流的问题模型
最大流问题就是在容量容许的条件下,从源点到汇点所能通过的最大流量。
- 通常可以把这些边想象成道路,流量就是这条道路的车流量,容量就是道路可承受的最大的车流量。很显然的,流量<=容量。而对于每个不是源点和汇点的点来说,可以类比的想象成没有存储功能的货物的中转站,所有“进入”他们的流量和等于所有从他本身”出去”的流量。在每条道路的流量<=容量的情况下,终点的车流量就是这这个图的最大流。
- 好比你家是汇 自来水厂是源点,然后自来水厂和你家之间修了很多条水管子接在一起,水管子规格不一 有的容量大 有的容量小,然后问自来水厂开闸放水 你家收到水的最大流量是多少,如果自来水厂停水了 你家那的流量就是0 当然不是最大的流量,但是你给自来水厂交了100w美金 自来水厂拼命水管里通水 但是你家的流量也就那么多不变了 这时就达到了最大流
现在我们也知道了最大流问题相关模型,我们知道了到底要求什么了,但是怎么求?
2.如何求最大流
我们先看一下这个图,很明显源点是‘0’,汇点是‘5’,下面我们来考虑如何求这个图的最大流。
首先,加入当前所有边上的流量都没有超过容量,那么就把这一组流量,或者说这个流,称为一个可行流。比如零流,即所有的流量都是‘0’的流。
我们就从这个零流开始考虑。假如有一条路,这条路从源点开始一段一段的连到了汇点,并且这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。那么我们一定能找到这条路上每一条边的增量(容量-流量)的最小值delta,我们把这条路上的所有边的流量都加上这个delta,一定可以保证这个流是可行的流,这是很显然的。
这样,我们就得到了一个更大的流,他的总流量是之前的流量+delta,而新增加流量的这条路叫做增广路
可以看上图:
刚开始所有边的流量都是零流,我们开始找他的增广路
0-1-3-5是一条可行的增广路,其delta=2,所以这条路增加的流量为delta
0-2-4-5也是一条增广路,这条路上的每条边增加的容量为1
…
我们不断的寻找增广路,每次都对其进行增广,直到源点和汇点不连通,也就是找不到增广路位置,当找不到增广陆的时候,当前的流量就是最大流!这结论非常重要。
其实以上就是求解最大流的基本思想
3.关于反向边
如果你之前看过其他的博客,应该已经知道了反向边是什么意思,但是这里我还是有必要再次说明的。
看一下这个网络流模型,当我们寻找增广路时,第一次我们可能找到了 1-2-3-4 这条路,我们对这条路进行增广,增广后我们发现从源点到汇点已经没有路可以走了,所以最大流是‘1’,但是我们明显可以看出来这个图的最大流是‘2’,为什么出现这种情况呢?
问题就在于我们没有给程序一个“后悔”的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。
这时候我们利用反向边的概念,每次增广以后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。
这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流‘2’。
那么,这么做为什么会是对的呢?
事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给“退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。
4.EK算法和Dinic算法
好了,我在上面已经把最大流的基本思路解释完了,相信你已经对其有所了解,那么我来说一下代码的具体实现,根据代码可以让你更好的了解最大流的算法
无论是EK还是Dinic,他们的算法原理都是寻找增广路直到无路可走,我们先总结一下大概思路:
- 寻找增广路,这条路从源点开始一直一段一段的连到了汇点,并且,这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。
- 更新残量图,将这条路径上的每一条有向边u->v的残量减去delta,同时对于起反向边v->u的残量加上delta
- 重复这个步骤,直到无路可走
4.1EK(Edmonds-Karp)算法
增广路径事实上是残留网中从源点s到汇点t的路径,可以利用图算法中的任意一种被算法来获取这条路径,例如BFS,DFS等。其中基于BFS的算法通常称为Edmonds-Karp算法,该算法是“最短”扩充路径,这里的“最短”由路径上的边的数量来度量,而不是流量或者容量。
基本思路:
- 找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是大于而不是大于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
- 找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
- 将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow
4.重复上述过程,直到找不出增广路,此时我们就找到了最大流
先看一下这道题 HDU排水沟
这是个模板题,那么我直接贴代码了,你可以通过代码进一步理解最大流EK算法解题思想
#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
const int N = 220;
const int inf=0x3f3f3f3f;
int flag[N],c[N*2][N*2],pre[N];
//flags数组存到这个点的最大流,c存容量,pre存上一个点
queue<int> q;
int m=0,n=0;
int bfs()
{
memset(pre,-1,sizeof(pre)); //初始化
pre[1] = 0;
flag[1] = inf; //先把起点流量定义为无限大
q.push(1);
while(!q.empty())
{
int s = q.front(); q.pop();
for(int i=1;i<=n;i++)
{
//如果这个点不是源点并且容量>0并且没有被使用过
if(i!=1&&c[s][i]&&pre[i]==-1)
{
pre[i] = s; //存上一个点
flag[i] = min(c[s][i],flag[s]);//记录当前delta和这个点的容量取最小,也就是求出当前的delta值
q.push(i);
}
}
}
if(pre[n]==-1) return -1; //如果汇点的标记还是-1,说明没有找到增广路,返回-1
return flag[n]; //返回增广路的delta值
}
int ek()
{
int sum = 0;
int a,b;
while(1)
{
int flow = bfs(); //
if(flow==-1) break;//如果没有找到增广路就退出
sum += flow; //最大流加上增广的delta值
int t = n;
while(t!=1)//更新残量图,从汇点更新到源点
{
a = pre[t];
c[a][t] -= flow;
c[t][a] += flow;
t = a;
}
}
return sum;
}
int main()
{
int l=0;
while(~scanf("%d %d",&m,&n))
{
memset(c,0,sizeof(c));
for(int i=0;i<m;i++)
{
int a,b,x;
scanf("%d %d %d",&a,&b,&x);
c[a][b] += x;
}
printf("%d\n",ek());
}
}
4.2Dinic算法
Dinic算法是基于EK算法的改进,并且两种算法都是由俄罗斯科学家Dinic提出的。Dinic在EK的基础上加入了层次优化,即用BFS分层后用DFS找增广路
算法思路
- 首先进行bfs分层网络
- 进行dfs多路增广,并且记录残量网络和流量。
- 重复上述过程直到不存在从s到t的路径为止。将所有dfs的结果累加起来就是答案。将每一次的增广路效果叠加起来就是图上每条边的流量。
说实话Dinic我还不太会啊啊啊啊啊,主要是存图的方法链式前向星还没看懂,下次我会写篇存图的方法链式前向星
请移步Dinic算法详述