求最大流的两种算法 EK算法和dinic算法

我们接下来讲的都是按照这个图来讲的

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的过程:


扫描二维码关注公众号,回复: 1435333 查看本文章

接下来,讨论一下为啥dinic算法比EK算法快,通过上面我写的图可以看到,在EK算法中,第一次进行bfs寻找增广路最小权值的操作中,2号顶点到3号顶点是可以走的。而在dinic算法中,第一次进行dfs寻找增广路最小权值的操作中,2号顶点到3号顶点是不可以走的,这就使得dinic算法比EK算法少了好多不必要的操作,从而加快了速度。

猜你喜欢

转载自blog.csdn.net/qq_40938077/article/details/80397118
今日推荐