最大流问题(模板)

一、问题描述

在下图的网络中,有一个源点机器 s s s有一个汇点机器 t t t,边的权值表示电缆的传输速度。我们的目标使求解源点 s s s到汇点 t t t的最大流量。

二、解题思路

贪心算法:

  • 看到这个问题;我们最简单的第一反应就是使用DFS的贪心算法;算法步骤:
    • 第一步:使用DFS搜索一条从源点 s s s到汇点 t t t的可以增流的路径(即路径中边的容量 c ( e ) > f ( e ) c(e)>f(e) c(e)>f(e));并记录下该路径上最多能增多少流(即路径上所有边的( c ( e ) − f ( e ) c(e)-f(e) c(e)f(e))的最小值)。
    • 第二步:进行增流,即修改路径上所有边的流量值 f ( e ) f(e) f(e)
    • 第三步:重复第一步和第二步,直到找不到可以增流的路径为止。
  • 当然了这种贪心算法肯定是错误的,现在我们来看一下这种算法错在哪里;假设我们对上图的网络进行实行这种贪心算法:
    这样我们得到的最大流为10.但该网络真正的最大流是11,如下图所示:
    观察一下两幅图的差异,我们可以发现:问题注意出在 1 → 2 1\rightarrow2 12边上,该边曾流过大,具体的错误是:在对路径 s → 1 → 2 → t s\rightarrow1\rightarrow2\rightarrow t s12t进行增流时增流过大。如果我们对路径 s → 1 → 2 → t s\rightarrow1\rightarrow2\rightarrow t s12t增流4,对路径 s → 1 → 3 → t s\rightarrow1\rightarrow3\rightarrow t s13t增流6,对路径 s → 2 → t s\rightarrow2\rightarrow t s2t增流1,就正确了。问题出在了增流的强度上,当时每天路径的曾流强度又是难以控制的。

Ford-Fulkerson算法:

  • 为了贪心算法的问题,我们对原图上的每条边增加一条反向的边,该条反向的边的容量 c ′ ( e ) c'(e) c(e)就等于该条边的流量 f ( e ) f(e) f(e) .
  • 反向边的用途(作者个人的一个理解):悔棋;我们进行贪心算法时每次都对路径做最大的增流,但是这种做法又是不是最优的,所有当贪心算法进行当前步骤时如果发现以前的步骤增流过多时反向边允许我们悔棋,即允许我们对以前的增加的流量进行撤销。
  • 经过贪心算法得到的流量的10的最大流,如上图所示,现在我们增加反向边,如下图所示再增流:
    向边 2 → 1 2\rightarrow1 21增流就相当于,撤销以前在边 1 → 2 1\rightarrow2 12增的流量。
  • Ford-Fulkerson算法步骤
    • 第一步:对原图的每条边增加一条反向边,原来边的权值为边的 c ( e ) − f ( e ) c(e)-f(e) c(e)f(e),反向边的权值 f ( e ) f(e) f(e);初始时 f ( e ) = 0 f(e)=0 f(e)=0
    • 第二步:按照贪心算法进行增流。
  • Ford-Fulkerson算法复杂度 O ( F ∣ E ∣ ) O(F|E|) O(FE)其中 F F F为最大流。

代码:

#include <iostream>
#include <vector>
#include<cstring>

using namespace std;
#define Max 1002
#define INF 999999


struct edge//边集结构体
{
    int to;//弧尾下标
    int cap;//边的权值
    int rev;//对应反向边的下标
};

vector<edge> G[Max];//图的邻接表
bool used[Max];

void add_adge(int from,int to,int cap)//向图中填加一条边及其反向边
{
    G[from].push_back((edge){to,cap,G[to].size()}) ;
    G[to].push_back((edge){from,0,G[from].size()-1}) ;
}

//DFS搜索可以增流的路径
int DFS(int v,int t,int f)
{//v:当前点;t:汇点;f:路径能增加的最大流量
    if(v==t)
        return f;
    used[v]=true;
    for(int i=0;i<G[v].size();i++)
    {
        edge& e=G[v][i];
        if(!used[e.to]&&e.cap>0)
        {
            int d=DFS(e.to,t,min(f,e.cap));
            if(d>0)
            {
                e.cap-=d;
                G[e.to][e.rev].cap+=d;
                return d;//每次DFS只更新一条路径
            }
        }
    }
    return 0;
}

int main()
{
    int N,M;//节点数和边数
    cin>>N>>M;
    int from,to,cap;
    for(int i=0;i<M;i++)
    {
        cin>>from>>to>>cap;
        add_adge(from,to,cap);
    }
    int s,t;
    cin>>s>>t;
    int flow=0;
    while(true)
    {
        memset(used,0,sizeof(used));
        int f=DFS(s,t,INF);
        if(f==0)
            break;
        flow+=f;
    }
    cout<<"最大流量为:"<<flow<<endl;
    return 0;
}

测试样例:
5 7
0 1 10
0 2 2
1 2 6
1 3 6
2 4 5
3 2 3
3 4 8
0 4
输出结果:
最大流量为:11
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值