- 通俗的讲,就是由若干个运货点,一个是起点,一个是终点,有一些运货点由路相连,每条路有容量限制,走过那条路时运送的货物不能超过其中的容量限制,求最大流就是求从起点运送尽量多的货物到终点,到达终点的货物个数。
- 增广路的意思是在当前网络之后找到一条能够从源点到汇点能运更多货物的路径。当一条边被增广之后(即它是增广路的一部分,或者说增广路通过这条边),这条边还能通过的流量,叫做剩余流量,修改之后的图称为残量网络。
- 这类算法的思路就是,一直找增广路,找到没有为止。
- 但是如果走完增广路,有更优的流法怎么办? 所以我们不能够单纯的寻找,需要给程序提供一个反悔的机会,在这里我们引入一个反向弧的概念,也就是在增广的同时,给增广路上的边的反向边容量增加所增广流量:意思是,程序可以反悔,把所流的流量流回去
最大流算法Dinic
Dinic算法是在找增广路 前提下加了几个优化,一次DFS的复杂度为O(n*m)。因此,Dinic算法的总复杂度即O(m*n*n)。
先给一个步骤,后面详细解释:
- 用BFS建立分层图
- 用DFS的方法寻找一条由源点到汇点的路径,获得这条路径的流量x.
- 根据这条路径修改整个图,将所经之处正向边流量减少x,反向边流量增加x
- 重复步骤2,直到DFS找不到新的路径时,重复步骤1
1、BFS
- 为了避免走重复的路径,首先从S点BFS,将整个图分成若干层,变成层次图,S点为第0层,定义dis[u]为u所在的层数,也就是至源点的最短距离。
- (tips:如果u点到下一个节点v的边剩余容量为0,则算法忽略此边,因为剩余容量为0已经无法增广)
- 如果在BFS的时候没有遍历过T点,则说明源点与汇点不连通,函数返回0,算法结束。有的话,返回1,代表建立成功,那么dis数组可以为接下来的DFS寻找增广路提供优化。
int bfs(int s, int t) //构建层次网络
{
memset(level, 0, sizeof(level));
level[s] = 1;
int front = 0, rear = 1;
q[front] = s; //使用队列bfs
while(front < rear)
{
int x = q[front++];
if(x == t) return 1;
for(int e = first[x]; e != -1; e = edge[e].next)
{
int v = edge[e].v, f = edge[e].f;
if(!level[v] && f) // !level[v]:每一个节点建立所属的最小层次
{ // f :保证有剩余容量,否则不能流通
level[v] = level[x] + 1;
q[rear++] = v;
}
}
}
return 0;
}
2、DFS
在BFS之后就要从源点开始DFS找增广路
DFS(u,exp)表示经过u点时当前流量为exp。刚开始时从源点DFS时,exp=∞,表示从该点流出的流量可以为无限大。
在DFS中遍历u所连接的点v,dis[v]必须等于dis[u]+1,否则会多走没有必要的路径。
可以利用DFS进行多路增广来保证时间效率,即一次增广多条路。
当u点不为T时,定义一个值flow为在u点所增广的流量,初始值为0
同时要找下一个节点以DFS(节点是一条以u为起点的边上的终点)
- 但需要一些限制:首先dis[v]=dis[u]+1,而且该边的剩余容量不能为0,否则肯定不能够增广更多的流量。
- 对于每次节点的DFS,如果该DFS可以增广的流量为0,说明增广失败,就直接跳过后面,找下一个节点v。
- 不为0的话,当前exp就减去增广路的流量,代表着在u点还可以流的流量,如果exp已经等于0就代表着在该点已经没有更多流量可以流过去了,必须退出增广,flow加上该增广路增广流量。
- 同时,正向边减去流量,代表剩余流量,反向边加上流量(反向弧)
- 最后,该点所有的节点遍历完后,返回该点所增广的流量flow给上一层。
整个DFS结束后,最大流累加器ans加上S点的增广流量,因为停止了DFS,
所以已经没有路可以增广,所以重新BFS给当前残余网络建层次图。
如果BFS返回0,即S点和T点不联通时,直接退出,输出当前ans,
也就是最大流
int dfs(int u,int exp){
if(u==T) return exp; //到达终点返回当前流量,就是该条增广路的最大流量
int flow=0,tmp= 0;
for(int i=head[u];i!=-1;i=g[i].nxt){
int v = g[i].v;
if((dis[v] == (dis[u]+1)) && (g[i].w>0)){
tmp = dfs(v,min(exp,g[i].w)); //tmp存的是v节点增广流量flow
if(!tmp) continue; //增广流量为0,则找下一个节点
exp -= tmp;
flow += tmp; //求出u节点的增广流量
g[i].w -= tmp; //正向边的容量减少
g[i^1].w += tmp; //反向边增加 用异或表示该边的反向边
if(!exp) break; //如果该点剩余流量为0就结束dfs
}
}
return flow; //返回u的增广流量
}
Dinic 源代码
注意:建i边时提前建立i的反向边 ,容量为0,且反向边编号为 i^1 (0^1=1,2^1=3,4^1=5),利用了异或性质,偶数异或1,会加一,所以,开始的编号一定为0,之后建立的所有正向边都为偶数编号,反向边为奇数编号。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
using namespace std;
#define inf 0x7fffffff
struct Edge{
int v,w,nxt;
}g[1001];
int head[1001];
int cnt;
void addEdge(int u,int v,int w){
g[cnt].v = v;
g[cnt].w = w;
g[cnt].nxt = head[u];
head[u] = cnt;
++ cnt;
}
int n,m,x,y,z;
int ans,flow;
int dis[1001];
queue<int> q;
int S,T;
void init(){
memset(head,-1,sizeof(head));
memset(g,0,sizeof(g));
cnt = 0;
memset(dis,-1,sizeof(dis));
while(!q.empty()) q.pop();
ans = 0;
}
int bfs(){
memset(dis,-1,sizeof(dis));
while(!q.empty()) q.pop();
dis[S] = 0;
q.push(S);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i=head[u];i!=-1;i=g[i].nxt){
int v = g[i].v;
if(dis[v]==-1 && g[i].w > 0){
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T]!=-1;
}
int dfs(int u,int exp){
if(u==T) return exp;
int flow=0,tmp= 0;
for(int i=head[u];i!=-1;i=g[i].nxt){
int v = g[i].v;
if((dis[v] == (dis[u]+1)) && (g[i].w>0)){
tmp = dfs(v,min(exp,g[i].w));
if(!tmp) continue;
exp -= tmp;
flow += tmp;
g[i].w -= tmp;
g[i^1].w += tmp; //反向边的容量
if(!exp) break;
}
}
return flow;
}
int main(){
while(~scanf("%d%d",&m,&n)){
init();
S = 1;T = n;
for(int i=1;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
addEdge(x,y,z);
addEdge(y,x,0);//反向边
}
while(bfs()){
ans += dfs(S,inf);
}
printf("%d\n",ans);
}
}
典型例题: