网络流--最大流的三大算法

【网络流】

我的理解就是网络流是计算在同一时间点的最大流通量。
举一个最经典的例子:
两点之间的最大容量是都是1(即在同一时间点上,只能通过一单位的流量),问你从点1到点4的最大流量。
这里写图片描述
其实就是2了,1–2–4 流经1单位的流量,1–3–4也流经1单位的流量,所以最大流量是2。
网络流基本上就是解决这一类的问题的,很简单吧~~~(这才是入门呢,继续学吧)。

下面介绍一些基础知识:
源点:网络流问的问题就是从源点到汇点的最大流,其实源点就是开始的点。
汇点:那汇点就是结束的点了。
容量:一条边所允许通过的最大流量就是这条边的容量了。
其他的概念不是特别重要。

再提一个问题:
如果我走的不是1–2–4
而是1–2–3–4呢?
这样的话,我们就没法继续走了,最大流就变成了1。
这里就要引入一个“反悔”的机制了,那就是反向边。
这里写图片描述
如图中所示:
将反向边的容量要加上该路中的最小的容量,这时正向边的容量要减去该路中的最小的容量。
这样就可以走1–3–2–4这条路了。
走完如图所示:
这里写图片描述
我们再看中间的路,正确 的答案应该是不走2–3这条路的,我们虽然用到了,但是他的最终结果与初始状态相同,所以可以看做是没有用到。这就是“反悔”机制,不太理解的可以再找个例子试试。

注意:最大流问题不一定只有一个解,例如:
这里写图片描述
有两种走法,你们可以想一想,图中没有标明点,只标了边的容量。

上面弄懂后,下面就讲解算法。
以这道题为例:

题目链接:Drainage Ditches

题目大意:
      给你n条边,m个点。
      每条边给出u,起始点,v,终止点,cap,边的容量.
问:从1到m的最大流量是多少。

邻接矩阵和邻接表的写法都有。

【Edmond—Karp】简称EK算法

这是我接触最早的算法。
先广搜深度最短的路,再更新这条路上的容量,直到找不到为止。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=1005;
int pre[maxn],mapp[maxn][maxn],visit[maxn],low_flow[maxn];
int n,m;

int find_max_flow(int s,int e)//查找有没有流量了
{
    memset(pre,-1,sizeof(pre));//记录路径
    memset(visit,0,sizeof(visit));//是否被访问过
    memset(low_flow,inf,sizeof(low_flow));//记录到这一点时的,最小流量(这样才能保证都能流通)。
    queue<int>q;
    q.push(s);
    visit[s]=1;
    low_flow[s]=inf;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(u==e) break;//遇到汇点就结束了。
        for(int i=1;i<=n;i++)
        {
            if(!visit[i]&&mapp[u][i])
            {
                q.push(i);
                visit[i]=1;
                low_flow[i]=min(low_flow[u],mapp[u][i]);
                pre[i]=u;
            }
        }
    }
    while(!q.empty())q.pop();
    if(pre[n]==-1) return 0;
    return low_flow[n];
}
void updata_flow(int v,int flow)//更新边的容量,这种方法可能较为耗时
{
    while(pre[v]!=-1)//用数组记录路径。
    {
        int u=pre[v];
        mapp[u][v]-=flow;//减去流经的流量,就相当于这条路的容量变小了
        mapp[v][u]+=flow;//同时建立反向边
        v=pre[v];
    }
}
int edmonds_karp(int s,int e)//算法主体
{
    int now_flow=0;//记录每一回的流量
    int max_flow=0;//记录所有的流量的和
    do
    {
        now_flow=find_max_flow(s,e);//查找流量
        updata_flow(e,now_flow);//更新每条边的容量(详见函数)
        max_flow+=now_flow;
    }while(now_flow);//当找不到,流量的时候就可以退出了,说明没有一个流量可以流通了
    return max_flow;
}

int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        memset(mapp,0,sizeof(mapp));

        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            mapp[u][v]+=w;//用邻接矩阵存储每条边的当前容量。
        }
        printf("%d\n",edmonds_karp(1,n));
    }
    return 0;
}

【Ford-Fulkerson】简称FF算法

简单的说就是一种暴力,深搜所有的路,直到无法找到为止。
代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn=1005;

int mapp[maxn][maxn];
int visit[maxn];
int n,m;
int dfs(int s,int t,int f)//深搜查找,寻找一条路
{
    if(s == t) return f;//找到汇点
    for(int i = 1 ; i <= n ; i ++)
    {
        if(mapp[s][i] > 0 && !visit[i])
        {
            visit[i] = true;
            int d = dfs(i,t,min(f,mapp[s][i]));//一直深搜,并且不断更新最小流量。
            if(d > 0)//d大于0,一定说明找到了一条路到汇点
            {
                mapp[s][i] -= d;//更新边的容量,并且建立反向边
                mapp[i][s] += d;
                return d;//返回这条路的最小流量
            }
        }
    }
    return 0;//只有当没有找到一条到汇点的路时,才会返回0。
}
int Ford_Fulkerson(int s,int t)//Ford-Fulkerson算法核心
{
    int flow = 0,new_flow=0;
    do
    {
        memset(visit,0,sizeof(visit));
        new_flow=dfs(s,t,INF);//深搜查找流量
        flow+=new_flow;
    }while(new_flow);//如果没有,说明没有流量了。
    return flow;
}
int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        memset(mapp,0,sizeof(mapp));//记录边的容量
        for(int i = 0 ; i < m ; i ++)
        {
            int from,to,cap;
            scanf("%d%d%d",&from,&to,&cap);
            mapp[from][to] += cap;
        }
        printf("%d\n",Ford_Fulkerson(1,n));
    }
    return 0;
}

【Dinic算法】最常用的算法—-速度较快

先建层,就如上面的图一样,1在第零层,2,3在第一层,4在第二层。
建完层后,用深搜,不过这回只能走相邻层,不能跳层,深搜所有可能的路径。
执行上面操作,直到无法建层为止。

代码:

#include <cstdio>
#include <string.h>
#include <queue>
using namespace std;
int const inf = 0x3f3f3f3f;
int const MAX = 205;
int n, m;
int mapp[MAX][MAX], flo[MAX];

int bfs(int s, int t)//将每个点建立层次
{
    //初始化
    queue<int> q;
    while(!q.empty()) q.pop();
    memset(flo, -1, sizeof(flo));

    flo[s] = 0;//将源点定义为0层
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int v = 1; v <= m; v++)
        {
            if(mapp[u][v] > 0 && flo[v] == -1)//如果点v没有被定义过层,并且u,v之间有边相连,就建层
            {
                flo[v] = flo[u] + 1;
                q.push(v);
            }
        }
    }
    return flo[t] != -1;//如果汇点没有被建层,说明没有流量能够到达汇点
}

int dfs(int u, int mi, int t)//查找路径上的最小流量
{
    if(u == t) return mi;
    for(int v = 1; v <= m; v++)
    {
        int tmp;
        //深搜,只在有u,v之间有边,且层数相差为1的时候,才能查找(而且必须是找到了汇点(tem不为0))。
        if(mapp[u][v] > 0 && flo[v] == flo[u] + 1&&(tmp= dfs(v, min(mi, mapp[u][v]), t)))
        {
            mapp[u][v] -= tmp;
            mapp[v][u] += tmp;
            return tmp;
        }
    }
    flo[u]=-1;//防止不必要的查找,从u点已经找不到汇点了,所以没有必要再次查找了,所以就将u的层次初始化,可以节省不少时间
    return 0;
}

int dinic()//主体
{
    int flow=0;//记录所有的流量
    while(bfs(1,m))//查找是否可以到达汇点(是否还有流量),并且建立层次
    {
        int new_flow=0;//记录当前的流量
        do
        {
            new_flow=dfs(1,inf,m);//深搜查找流量
            flow+=new_flow;
        }
        while(new_flow); //没有流量了
    }
    return flow;
}

int main()
{
    while(~scanf("%d %d", &n, &m))
    {
        memset(mapp, 0, sizeof(mapp));
        int u, v, w;
        while(n--)
        {
            scanf("%d%d%d",&u,&v,&w);
            mapp[u][v] += w;
        }
        printf("%d\n", dinic());
    }
    return 0;
}

以上都是用邻接矩阵写的(适用于稠密图),下面是用邻接表写的(适用于稀疏图)

【Dinic算法】

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
int const inf = 0x3f3f3f3f;
int const maxn = 2050;
int flo[maxn];
int firs[maxn];
struct node
{
    int v,cap,nex;
} edge[maxn*maxn];
int N;
void merget(int u,int v,int cap)
{
    edge[N].v=v;
    edge[N].cap=cap;
    edge[N].nex=firs[u];
    firs[u]=N++;

    edge[N].v=u;//提前建立反向边
    edge[N].cap=0;
    edge[N].nex=firs[v];
    firs[v]=N++;
}

bool bfs(int s, int t)
{
    queue<int>q;
    while(!q.empty())
        q.pop();
    memset(flo,-1,sizeof(flo));
    flo[s] = 0;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=firs[u]; i!=-1; i=edge[i].nex)
        {
            int v=edge[i].v,cap=edge[i].cap;
            if(flo[v]==-1&&cap)
            {
                flo[v] = flo[u] + 1;
                q.push(v);
            }
        }
    }
    return flo[t]!=-1;
}

int dfs(int u, int mi, int t)
{
    if(u == t)
        return mi;
    for(int i =firs[u]; i!=-1; i=edge[i].nex)
    {
        int tmp;
        int v=edge[i].v,cap=edge[i].cap;
        if(cap&&flo[v] == flo[u] + 1&&(tmp= dfs(v, min(mi,cap),t)))
        {
            edge[i].cap-=tmp;
            edge[i^1].cap+=tmp;
            return tmp;
        }
    }
    flo[u]=-1;
    return 0;
}

int dinic(int s,int t)//主体
{
    int flow=0;//记录所有的流量
    while(bfs(s,t))//查找是否可以到达汇点(是否还有流量),并且建立层次
    {
        int new_flow=0;//记录当前的流量
        do
        {
            new_flow=dfs(s,inf,t);//深搜查找流量
            flow+=new_flow;
        }
        while(new_flow); //没有流量了
    }
    return flow;
}

int main()
{
    int n, m;
    while(~scanf("%d%d",&n,&m))
    {
        memset(firs,-1,sizeof(firs));
        N=0;
        int u, v, w;
        for(int i=0; i<n; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            merget(u,v,w);
        }
        printf("%d\n",dinic(1,m));
    }
    return 0;
}

【Edmond—Karp】

#include<stdio.h>
#include<queue>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
const int inf=0x3f3f3f3f;
int n,m;
int tot;
int pre[maxn],res[maxn];
int minn[maxn];
int vis[maxn];
struct node
{
    int v,w;
    int nex;
} e[maxn];
int first[maxn];
void edge(int u,int v,int w)
{
    e[tot].v=v;
    e[tot].w=w;
    e[tot].nex=first[u];
    first[u]=tot++;

    e[tot].v=u;
    e[tot].w=0;
    e[tot].nex=first[v];
    first[v]=tot++;
}
int bfs(int s)
{
    memset(vis,0,sizeof(vis));
    memset(minn,inf,sizeof(minn));
    memset(pre,-1,sizeof(pre));
    memset(res,-1,sizeof(res));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(!q.empty())
    {
        int p=q.front();
        q.pop();
        if(p==n)
        {
            return minn[n];
        }
        int k=first[p];
        for(int i=k; i!=-1; i=e[i].nex)
        {

            int x=e[i].v;//printf("%d %d\n",p,x);
            if(!vis[x]&&e[i].w>0)
            {
                minn[x]=min(minn[p],e[i].w);
                vis[x]=1;
                res[x]=i;
                pre[x]=p;
                q.push(x);
            }
        }
    }
    return -1;
}
int EK()
{
    int sum=0;
    while(1)
    {
        int now=bfs(1);
        //printf("%d\n",now);
        if(now!=-1)
        {
            sum+=now;
            int k=n;
            while(k!=1)
            {
                e[res[k]].w-=now;
                e[res[k]^1].w+=now;
                k=pre[k];
            }
        }
        else
        {
            break;
        }
    }
    return sum;
}
int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        memset(first,-1,sizeof(first));
        tot=0;
        for(int i=0; i<m; i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            edge(u,v,w);
        }
        printf("%d\n",EK());
    }
    return 0;
}

【Ford-Fulkerson】

#include<stdio.h>
#include<stack>
#include<queue>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
const int inf=0x3f3f3f3f;
int n,m;
int tot;
int pre[maxn],res[maxn];
int minn[maxn];
int vis[maxn];
int d;
struct node
{
    int v,w;
    int nex;
} e[maxn];
int first[maxn];
void edge(int u,int v,int w)
{
    e[tot].v=v;
    e[tot].w=w;
    e[tot].nex=first[u];
    first[u]=tot++;

    e[tot].v=u;
    e[tot].w=0;
    e[tot].nex=first[v];
    first[v]=tot++;
}
int dfs(int s,int t,int d)
{
    //printf("%d %d\n",s,t);
    if(s==t) return d;
    vis[s]=1;
    //printf("s=%d\n",s);
    for(int i=first[s]; i!=-1; i=e[i].nex)
    {

        int p=e[i].v;
        //printf("p=%d\n",p);
        if(!vis[p]&&e[i].w>0)
        {
            //printf("p=%d\n",p);
            int dd=dfs(p,t,min(d,e[i].w));
            if(dd>0)
            {
                e[i].w-=dd;
                e[i^1].w+=dd;
                return dd;
            }
        }
    }
    return 0;
}
int FF()
{
    int sum=0;
    while(1)
    {
        //printf("%d\n",sum);
        memset(vis,0,sizeof(vis));
        int now=dfs(1,n,inf);
        //printf("%d\n",now);
        if(now!=0)
        {
            sum+=now;
//            int k=n;
//            while(k!=1)
//            {
//                e[res[k]].w-=now;
//                e[res[k]^1].w+=now;
//                k=pre[k];
//            }
        }
        else
        {
            break;
        }
    }
    return sum;
}
int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        d=inf;
        memset(first,-1,sizeof(first));
        tot=0;
        for(int i=0; i<m; i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            edge(u,v,w);
        }
        printf("%d\n",FF());
    }
    return 0;
}

有错误请留言!!

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值