高级篇——Dinic最大流算法

起于源点s,止于汇点t,解决最大流问题的过程就是不断寻找增广路径的过程。最大流问题专用术语先一一解释。

1.增广路径:从源点到汇点不一定只有一条路,要想做到流到汇点的流量最大,必须使得每一条能到汇点的路径都能被流经。每一条从源点到汇点的路径便是一条增广路径。

2.反向弧:每从一点到达另一点,都需要在相反的方向上设置一条反向边,每条反向边的作用可以理解为给算法一个可以反悔的机会。

3.残余网络:当每从一点到达其下一个点时,这个点可以经过的流量需要减去实际经过的流量,以便往后寻找增广路径时不会出现流量大于可行流量的情况。这样处理过的流量网络便是残余网络。

最大流问题较为高效的方法是Dinic和SAP,Dinic方法结合了中级篇讲的BFS和DFS算法的优势,通过BFS算法寻找增广路径,DFS算法求得每条可行流的最大值。

先来讲讲如何寻找增广路径。

增广路径用BFS算法。设置数组dis[ ],表示每个点距离源点的距离。初始化为-1,源点dis[ 1 ] =1,之后开始遍历,一要是能到达且该点对应dis值为-1,便将该点dis值设为前点dis值加1。这个有什么用?等到了DFS的时候就知道了 。如果最终汇点的dis值不为-1,及说明会点可以到达,增广路径存在,返回true,否则返回false。

int BFS()
{
    queue<int>que;
    memset(dis,-1,sizeof(dis));//每次寻找增广路径都需要将dis数组重置
    dis[1]=0;   //起始点为0
    que.push(1);
    while(!que.empty())
    {
        int cur=que.front();
        que.pop();
        for(int i=1;i<=n;i++)
        {
            if(c[cur][i]&&dis[i]<0) //如果可行且该点尚未被标注
            {
                dis[i]=dis[cur]+1;  //改变dis值
                que.push(i);        //入队继续遍历
            }
        }
    }
    if(dis[n]>0) return true;  //汇点值不为-1,说明增广路径存在
    return false;              //反之不存在
}

BFS负责寻找增广路径,DFS计算可行的最大流,此时dis[ ]就派上用场了。一旦经过BFS发现存在可行流,现在要做的便是从源点出发,只要遇到可以到达且dis[ 终点 ]=dis[ 起点 ]+1便继续对该终点进行DFS搜索。也就是说DFS是一个不断递归的过程。当终点为汇点时返回对应的流量值,并构建残余网络和反向弧。

int DFS(int point,int MAX) //point为当前遍历的节点,MAX为最大可行流量,MAX初始为INF
{
    int a;
    if(point==n) return MAX; //当前点为汇点时返回即可
    for(int i=1;i<=n;i++)
    {
        //当路径可行,对终点进行DFS
        if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i]))))
        {
            c[point][i]-=a; //构建残余网络
            c[i][point]+=a; //建立反向弧
            return a;
        }
    }
    return 0;
}

以POJ 1273为例:http://poj.org/problem?id=1273

题意:m条路径,n个点,每条路径输入起点,终点和流量,求源点到汇点的最大流量

分析:这题直接套模板就行

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#define INF 9999999
using namespace std;
int n,m;
int c[205][205],dis[205]; //c数组为原始网络

int BFS()
{
    queue<int>que;
    memset(dis,-1,sizeof(dis));//每次寻找增广路径都需要将dis数组重置
    dis[1]=0;   //起始点为0
    que.push(1);
    while(!que.empty())
    {
        int cur=que.front();
        que.pop();
        for(int i=1;i<=n;i++)
        {
            if(c[cur][i]&&dis[i]<0) //如果可行且该点尚未被标注
            {
                dis[i]=dis[cur]+1;  //改变dis值
                que.push(i);        //入队继续遍历
            }
        }
    }
    if(dis[n]>0) return true;  //汇点值不为-1,说明增广路径存在
    return false;              //反之不存在
}
int DFS(int point,int MAX) //point为当前遍历的节点,MAX为最大可行流量,MAX初始为INF
{
    int a;
    if(point==n) return MAX; //当前点为汇点时返回即可
    for(int i=1;i<=n;i++)
    {
        //当路径可行,对终点进行DFS
        if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i]))))
        {
            c[point][i]-=a; //构建残余网络
            c[i][point]+=a; //建立反向弧
            return a;
        }
    }
    return 0;
}

int main()
{
    while(cin>>m>>n)
    {
        memset(c,0,sizeof(c));
        while(m--) //构建原始网络
        {
            int u,v,w;
            cin>>u>>v>>w;
            c[u][v]+=w;
        }
        int sum=0;
        while(BFS()) //当增广路径存在进行DFS
        {
            int s=DFS(1,INF); //从源点出发,初始最大可行流设为无穷大
            sum+=s;  //结果加上得到的最大可行流
        }
        cout<<sum<<endl;
    }
    return 0;
}


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值