Ek求最大流

本文深入解析EK算法(Edmonds-Karp算法),介绍了最大流问题及其核心思路,包括残留网络、增广路和增广路定理。通过示例详细解释了算法流程,展示了如何在图中寻找增广路径并更新边的容量,直至找到最大流。最后,给出了一个EK算法的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

EK求最大流


题目描述

image-20210805144728255


核心思路

残留网络:残留网络G∗G^*G与原网络GGG的节点相同,GGG中的每条边都对应G∗G^*G中的一条或两条边。在残留网络中,与原网络对应的同向边是可增量(还可以增加多少流量),反向边是实际流量。

在残留网络中不显示0流量边,网络GGG及可行流对应的残留网络G∗G^*G如下图:

image-20210805151526564

如下图所示,第一次从v1v_1v1v3v_3v3流出了flow为6的流量,那么由于最大容量cap=10,那么下次还可以从v1v_1v1v3v_3v3流出flow为10−6=410-6=4106=4的流量。反向边是实际流量可以理解为:v1v_1v1后悔把6这个流量送给v3v_3v3了,然后v3v_3v3就又把6这个流量归还给了v1v_1v1

image-20210805151610067

增广路是指残留网络上从源点SSS到汇点TTT的一条简单路径。增广量是指增广路径上每条边都可以增流的最小值。

如下图所示,s→v1→v3→ts\to v_1\to v_3\to tsv1v3t就是一条增广路,增广量是这条增广路径上各边的最小值min(9,5,12)=5min(9,5,12)=5min(9,5,12)=5。为什么是取5而不是取9或者12呢?因为比如假设从源点SSS流出了流量9,但是由于v1→v3v_1\to v_3v1v3这条水管最多承受流量5,因此当我们把流量9送到这条水管时,这条水管就爆了,不能运输水了,因此是取这条增广路径上各边的最小值。

image-20210805151639509

增广路定理:设flowflowflow是网络GGG的一个可行流,如果不存在从源点到汇点的增广路,则flowflowflowGGG的一个最大流。

增广路算法的基本思想:在残留网络中寻找增广路,然后在实流网络中沿增广路增流,在残留网络中沿增广路减流,重复以上步骤,直到不存在增广路时为止。此时,实流网络中的可行流就是所求的最大流。

image-20210805151801874

如上图所示。图一是原网络中每条边的初始容量。EK算法找到了流量为5的增广路S→C→D→B→TS\to C\to D\to B\to TSCDBT。于是在图二中,剩余容量对应发生了变化。接着,图三中,EK算法又找到了流量为2的增广路S→A→B→D→E→TS\to A\to B\to D\to E\to TSABDET。最终在图四中,网络就不存在增广路了,最大流的大小就是5+2=75+2=75+2=7。图中省略了剩余容量为0的边。

问题:如何理解i=e[pre[i]^1]

如下图

image-20210805152509340


代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010,M=20010,INF=1e8;
int n,m,S,T;
//f是边上的容量
int h[N],e[M],ne[M],f[M],idx;
//q是宽搜存放点的队列
//d[i]=x表示从起点S到点i这条增广路上各边的最小剩余容量
//pre数组记录的前驱边而不是前驱点 比如pre[ver]=i表示节点ver是从边i走过来的
//即节点ver的前驱边是 边i
int q[N],d[N],pre[N];
//宽搜中判断某个点是否已经被访问过了
bool st[N];

void add(int a,int b,int c)
{
    //正向边 初始化还没有流出流量 所以正向边课流出的容量保持不变 仍为c
    e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
    //反向边 初始化还没有流出流量 所以反向边没有流量  仍为0
    e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;  
}

bool bfs()
{
    memset(st,0,sizeof st);
    int hh=0,tt=0;
    //由于d是想要求最小  所以一般初始化为无穷大
    q[0]=S,st[S]=true,d[S]=INF;
    //进行宽搜
    while(hh<=tt)
    {
        int t=q[hh++];  //取出队头元素
        //遍历节点t的所有邻接点
        for(int i=h[t];~i;i=ne[i])  //i是边
        {
            int ver=e[i];//ver是节点t的邻接点
            //如果节点ver还没有被访问过 并且i这条边的容量仍然是大于0
            if(!st[ver]&&f[i])
            {
                st[ver]=true;
                //d[t]表示从起点到节点t这条增广路径上的各边中的最小剩余容量
                //f[i]表示i这条边的容量
                //d[ver]表示从起点S到点ver这条增广路上各边的最小剩余容量
                d[ver]=min(d[t],f[i]);
                //pre[ver]=i表示节点ver是从边i走过来的 即节点ver的前驱边是 边i
                pre[ver]=i;
                //如果走到了终点  则找到了一条从起点S到终点S且容量>0的增广路径
                if(ver==T)
                    return true;
                q[++tt]=ver;
            }
        }
    }
    //没有找到增广路径
    return false;
}

int EK()
{
    int maxflow=0;  //最大流
    //如果一直能找到增广路径  则一直寻找 同时累加每一条增广路径上的d[T]
    //最终就是这张图的最大流
    while(bfs())
    {
        //累加不同的增广路径上的d[T]
        maxflow+=d[T];
        //从终点T逆向到起点S  更新正向边和反向边的容量
        for(int i=T;i!=S;i=e[pre[i]^1])
        {
            //由于已经流出了d[T],那么pre[i]这条边还可以流出f[pre[i]]-d[T]的流量
            f[pre[i]]-=d[T];    //更新正向边的容量
            f[pre[i]^1]+=d[T];  //更新反向边的容量
        }
    }
    //到这里说明不能再找到一条增广路径了  那么此时就求出了最大流
    return maxflow;
}

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d%d%d",&n,&m,&S,&T);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d\n",EK());
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计小酱蟹不肉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值