我们接下来讲的都是按照这个图来讲的
EK算法
算法思想,基本流程:
通过广搜函数寻找增广路,然后函数返回的是找到的增广路中权值最小的值,如果找不到增广路,返回0。然后运用广搜函数返回的值更新邻接矩阵中的值。每次都把广搜函数返回的值累加,直到图中不存在增广路为止,累加的结果就是最大流。
代码如下:
#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
int a[MAXN][MAXN];//邻接矩阵
int pre[MAXN];//pre[i]代表哪个点到达的i点,pre数组相当于记录一条路径
int n,m;//n个顶点,m条边
int u,v,w;//u点到v点的距离为w
int sum=0;//最后的结果(最大流)
int s,h;//s代表起点,h代表汇点
int visited[MAXN];//标记数组,判断i点是否走过
void genggai(int k)//更新邻接矩阵中的值
{
int p=h;
while (pre[p]!=-1)
{
a[pre[p]][p]-=k;
a[p][pre[p]]+=k;//这一行代码是设置反向边,给ek函数一个反悔的机会
p=pre[p];
}
}
int ek()//算法的核心部分,通过广搜寻找一条增广路,如果函数返回0,说明找不到增广路了
{
int minz=MAXN;
queue < int >q;
q.push(s);//将起点放入队列中去
visited[s]=1;//标记为已经走过的
while (!q.empty())
{
int k=q.front();
q.pop();
if(k==h)//如果当前经过的点等于汇点,直接返回
return minz;
else
{
for(int i=1;i<=n;i++)//依次遍历所有点
{
if(!visited[i]&&a[k][i])//条件一:i点之前没有被走过。条件二:k点和i点之间能够直接连通
{
pre[i]=k;//i的前一个点是k点
minz=min(minz,a[k][i]);//更新minz,实际上是找出pre数组所记录的路径中权值最小的一个数(函数返回的也是这个数)
visited[i]=1;//标记为已经走过
q.push(i);//放入队列中去
}
}
}
}
return 0;
}
int main()
{
cin>>n>>m;
memset(a,0,sizeof(a));//初始化邻接矩阵
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
a[u][v]=w;//给邻接矩阵赋值
}
cin>>s>>h;//输入终点和起点
while (1)
{
int sum1=sum;
memset(visited,0,sizeof(visited));//每次进入ek函数都得将visited数组和pre数组初始化。因为visited数组和pre数组只对当前这一次函数调用有作用
memset(pre,-1,sizeof(pre));
int k=ek();
sum+=k;
if(sum1==sum)//这个条件满足,说明sum的值没有改变,说明ek函数返回的是0,说明找不到增广路了,结束while循环
break ;
genggai(k);//更新邻接矩阵中的值
}
cout<<sum<<endl;
}
代码运行过程:
注意:
通过广搜的性质可以知道,每次寻找增广路时,都是寻找的汇点到起始点边的条数最小的路径,直到该路径上有条边的权值变成了0为止。通过上述图可知,我们第一次找到的增广路是1-->3-->5。但是这个时候找到的最小值却是3,不是4。因为通过程序可知1号顶点出队后,2号和3号入队,此时最小值为4。然后2号出队,4号入队,此时最小值是2号到4号顶点的权值3,然后3号出队,5号入队,但是4大于3,所以最小值不变,然后4号出队,没有点入队,最后5号点出队,bfs函数结束,返回3。然后更新邻接矩阵,此时1-->3-->5这条路径上每条边的权值都减3,反向边加3,然后再一次进行bfs,这一次进行完bfs后,找到的增广路径还是1-->3-->5,最小值为1。然后在更新邻接矩阵中的值,这次更新完成后,3号到5号这条边的权值变成0,所以下一次进行bfs找到的增广路径就换了。
dinic算法
算法思想,基本流程:
该算法的核心思想是通过bfs给每个点编号(该点到起始点最短的边的个数)(其实bfs过程不仅是对每个点编号,还能够查看当前图中是否还存在增广路),然后运用dfs求出增广路中最小的权值,并且返回该最小的权值,累加。
注意:
bfs是多次进行的,每通过dfs求出增广路中最小权值后,然后就要更新邻接矩阵,然后还要进行bfs重新给各个顶点编号
代码如下:
#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
int a[MAXN][MAXN];//邻接矩阵
int n,m;//n个顶点,m条边
int u,v,w;//u点到v点的距离为w
int dis[MAXN];//dis[i]代表i点到起点最少有多少条边(给i点标号)
int s,h;//s代表起点,h代表汇点
int sum=0;//最大流
int minz=MAXN;
int bfs()//bfs是给各个顶点标号的(判断图中是否还存在增广路,如果存在,返回1。否则,返回0),即往dis数组中填充值,
{
memset(dis,-1,sizeof(dis));//初始化为-1
queue < int >q;
q.push(s);//将起点放入到队列中去
dis[s]=0;//s点到起点的边的条数为0条
while (!q.empty())
{
int u=q.front();
q.pop();
for(int i=1;i<=n;i++)
{
if(dis[i]<0&&a[u][i])//第一个条件如果满足,说明i点还没有被标记过。第二个条件如果满足,说明u点到i点能够直接连通
{
dis[i]=dis[u]+1;
q.push(i);
}
}
}
if(dis[h]>0)//如果这个条件满足,说明图中还存在增广路,返回1
return 1;
return 0;
}
int dfs(int point,int a1)//point代表当前访问的点,a1每次存的是这条路径上最小的权值
{
int minz;
if(point==h)//如果当前点等于汇点,直接返回这条路径上最小的权值
return a1;
for(int i=1;i<=n;i++)//依次遍历所有的点
{
if(a[point][i]&&dis[i]==dis[point]+1&&(minz=dfs(i,min(a1,a[point][i]))))
{
a[point][i]-=minz;//更新邻接矩阵
a[i][point]+=minz;//建立反向边
return minz;//如果程序能运行到这里,(存在增广路的前提下)说明这条路径已经访问完了,找到了这条路径上的最小权值了。剩下的就是更新邻接矩阵了
}
}
return 0;//如果程序从这里返回,说明从起始点到当前点的这条路径不是增广路,返回,寻找其他的路径。
}
int main()
{
while (cin>>n>>m)
{
memset(a,0,sizeof(a));
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
a[u][v]=w;
}
cin>>s>>h;
while (bfs())//这个地方必须多次进行,因为如果找到了一条增广路的话,临街矩阵就会更新里面的值,有的点到起点的边的条数可能会减少
{
int k=dfs(s,MAXN);
sum+=k;
}
cout<<sum<<endl;
}
}
代码执行过程 自己手写的,字有点难看,见谅,只写了第一次执行dfs的过程:
接下来,讨论一下为啥dinic算法比EK算法快,通过上面我写的图可以看到,在EK算法中,第一次进行bfs寻找增广路最小权值的操作中,2号顶点到3号顶点是可以走的。而在dinic算法中,第一次进行dfs寻找增广路最小权值的操作中,2号顶点到3号顶点是不可以走的,这就使得dinic算法比EK算法少了好多不必要的操作,从而加快了速度。