因为网上介绍网络流和最大流理论的文章非常的多,也都解释的非常清晰并附带图解。再加上绘图技术不佳,语文表达极差,就只讲解一下程序实现部分。想看理论讲解及算法正确性证明的读者可以上网搜一搜,这里推荐一篇博客,链接如下:
http://blog.csdn.net/mystery_guest/article/details/51910913
那么,既然网上的资源那么多,为什么还要写这篇博客呢?一是为了凑数做笔记,二是网上的许多算法模板都十分工程化,变量名贼长,代码量巨大也不容易理解,在博主初学时带来了不小麻烦。于是,本着能水一篇是一篇普度众生的情怀,博主在这里就讲解一下,最大流的几种算法与自认为很简洁的代码。
1.Ford-Fulkerson(二F)算法
因为本算法博主并没有学比较简单且时间复杂度极高,基本被算法竞赛淘汰了,故不多作讲解,感兴趣的读者请自行搜索。
2.Edmonds-karp(EK)算法
Ek算法最多处理5000条边的网络,时间复杂度仍然较高,近几年基本用不到了,只能作为初学网络流的oier增广路算法和残量网络等网络流基本概念的练手,例题如下:
全流Total Flow(来源:https://www.luogu.org/problemnew/show/2936)
输入输出样例
输入样例#1:
5
A B 3
B C 3
C D 5
D Z 4
B Z 6
输出样例#1:
3
反正这个题只是让大家检验一下自己编的算法正确性的,博主直接上代码,本篇博客的重点放在下一个算法,代码如下:
#include<bits/stdc++.h>
using namespace std;
int sq[300][300];//存图
int flow[300];//存流量
int pre[300];//记录增广路路径
int n;
queue<int>dui;//bfs队列
int bfs(int s,int e)//bfs寻找增广路
{
while(!dui.empty())
{
dui.pop();
}
for(int i=1;i<=60;i++)
{
pre[i]=-1;
}
pre[s]=0;
flow[s]=1e9;
dui.push(s);
while(!dui.empty())
{
int lo=dui.front();
dui.pop();
if(lo==e)
{
break;
}
for(int i=1;i<=60;i++)
{
if(i!=s&&sq[lo][i]>0&&pre[i]==-1)
{
pre[i]=lo;//记录上一个点
flow[i]=min(sq[lo][i],flow[lo]);//寻找增广路上的最小流量
dui.push(i);
}
}
}
if(pre[e]==-1)
{
return -1;//如果终点没有标记则无法增广,返回负值
}
return flow[e];//返回最小流量
}
int maxf(int s,int e)
{
int in=bfs(s,e),sum=0;
while(in!=-1)//若存在增广路就一直bfs增广
{
int k=e;
while(k!=s)//从最后一个点往回跳
{
int lo=pre[k];
sq[lo][k]-=in;
sq[k][lo]+=in;//修改该路径上的流量
k=lo;
}
sum+=in;//记录流量
in=bfs(s,e);
}
return sum;
}
int main()
{
int a,b,c;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{//普通的建图
char ch1[2],ch2[2];
int k,s=0;
scanf("%s%s%d",ch1,ch2,&c);
a=ch1[0]-64;
b=ch2[0]-64;
sq[a][b]+=c;
}
printf("%d",maxf(1,26));
return 0;
}
就这样把两个算法水过了,相信大家一定受益匪浅,那么接下来就进入本篇博客的重点。
3.Dinic算法
Dinic能够处理大概500000条边的最大流问题,对于不太刁钻的数据,1000000条边也是可以开个O(3)优化过的,在算法竞赛中还是比较实用,代码量也比较小,相比于EK而言只多了30行/300byte(其实应该更小,由于博主的辣鸡格式所以行数较多)。
依然给出例题,是个纯模板题大家可以交上去检验一下。
网络最大流(来源:https://www.luogu.org/problemnew/show/3376)
输入输出样例
输入样例#1:
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例#1:
50
Dinic算法的主要优化,就是加了一个bfs分层,将网络分成多个层次,每次dfs寻找增广路时都只搜索下一层,返回的时候修改残量网络里的值。
定义结构体/数组如下
const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge{
int fl,to;//fl记录该边的流量,to为该边的终点
};
edge ed[maxn<<1];//储存边,注意要开两倍大小
int g=0;//记录边的数量,只在加边的时候有用
vector<int>sq[maxn];//储存已该为起点的边的编号
这个结构体只用维护两个量,相较网上的其他结构体比较简单。
接下来是加边的函数,代码如下:
void add(int x,int y,int a)
{
ed[g]=(edge){a,y};//相当于将一个fl=a,to=y的edge类型变量放进ed数组
sq[x].push_back(g++);//将这个边的编号push进去
ed[g]=(edge){0,x};
sq[y].push_back(g++);//同上
}
到目前为止,dinic都十分简单,接下来是bfs分层函数,跟普通的bfs相比只改动了一点,应该也很好懂,上代码,详细讲解见注释:
int d[maxn];
bool bfs(int s,int e)//s起点,e终点
{
memset(d,0,sizeof(d));
queue<int>dui;
dui.push(s);
d[s]=1;//初始化
while(!dui.empty())
{
int lo=dui.front();
dui.pop();
for(int i=sq[lo].size()-1;i>=0;i--)//普通的bfs
{
int hh=sq[lo][i];//记录对应边的编号
int to=ed[hh].to;
if(!ed[hh].fl||d[to])//如果边的流量为0或已经目标点已经标记,跳过
{
continue;
}
d[to]=d[lo]+1;
dui.push(to);//普通的bfs
}
}
return d[e];//返回终点的层数,没有标记过就会返回false,其他都是true
}
跟普通bfs完全只有一行代码有区别对吧,相信大家依然活力十足,想要继续读完这篇博客(手动滑稽),接下来就是dfs找增广路了。
int dfs(int s,int e,int minn)//s起点,e终点,minn记录增广的值
{
if(s==e)
{
return minn;//到达终点就回溯
}
int ans=0;
for(int i=sq[s].size()-1;i>=0;i--)
{//常规dfs
int hh=sq[s][i];
int fl=ed[hh].fl;
int to=ed[hh].to;
if(d[to]!=d[s]+1||!fl)//如果目标点不在下一层或者流为0,跳过
{
continue;
}
int tmp=dfs(to,e,min(minn-ans,fl));//递归搜索
if(!tmp)//如果返回值已经为0,直接进入下一个递归,不再回溯
{
continue;
}
ed[hh].fl-=tmp;
ed[hh^1].fl+=tmp;//修改边的流
ans+=tmp;//记录增广的值
}
return ans;
}
Dinic的主要函数都已经讲的差不多了,依然非常的简单,最后来个dinic函数解决战斗
int dinic(int s,int e)
{
int ans=0;
while(bfs(s,e))//如果有终点依然可以到达,就一直增广
{
ans+=dfs(s,e,inf);//记录流量
}
return ans;
}
在最最最后,贴上博主自认为简洁的代码,方便大家复制。
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge{
int fl,to;
};
edge ed [maxn<<1];
vector<int>sq[maxn];
int g=0;
void add(int x,int y,int a)
{
ed[g]=(edge){a,y};
sq[x].push_back(g++);
ed[g]=(edge){0,x};
sq[y].push_back(g++);
}
int d[maxn];
bool bfs(int s,int e)
{
memset(d,0,sizeof(d));
queue<int>dui;
dui.push(s);
d[s]=1;
while(!dui.empty())
{
int lo=dui.front();
dui.pop();
for(int i=sq[lo].size()-1;i>=0;i--)
{
int hh=sq[lo][i];
int to=ed[hh].to;
if(!ed[hh].fl||d[to])
{
continue;
}
d[to]=d[lo]+1;
dui.push(to);
}
}
return d[e];
}
int dfs(int s,int e,int minn)
{
if(s==e)
{
return minn;
}
int ans=0;
for(int i=sq[s].size()-1;i>=0;i--)
{
int hh=sq[s][i];
int fl=ed[hh].fl;
int to=ed[hh].to;
if(d[to]!=d[s]+1||!fl)
{
continue;
}
int tmp=dfs(to,e,min(minn-ans,fl));
if(!tmp)
{
continue;
}
ed[hh].fl-=tmp;
ed[hh^1].fl+=tmp;
ans+=tmp;
}
return ans;
}
int dinic(int s,int e)
{
int ans=0;
while(bfs(s,e))
{
ans+=dfs(s,e,inf);
}
return ans;
}
int main()
{
int n,m,s,e;
scanf("%d%d%d%d",&n,&m,&s,&e);
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
printf("%d",dinic(s,e));
return 0;
}