有向图最大流EK算法(水流木桶原理)

20 篇文章 0 订阅
2 篇文章 0 订阅

前言

网络中的交换机能够实时的进行数据的接收转发,如果我们要从A地传送一组数据报到达B地,在不存在丢包的前提下一次分发B地最多能接收多少报文呢?这种网络模型可以利用水流的模拟来解决,学名称网络流。


算法介绍

总所周知,水往低处流。每个节点可以抽象成一个木桶,所以一次能通过的流量是有瓶颈的,根据木桶原理,我们每次可以流过的流量取决于前面最小的弧。这里很容易走进一个误区,贪心地每次去找下一条路径的最大的流过去,这是错误的,至于为什么很容易想到一些例子推翻,试想,一个水流到达一个分叉路假设水流足够肯定是两边都会有水灌下去。
于是,我们算法地核心思路是:

  • 每次找一条增广路,然后灌水下去,至于灌的水为: a [ e . t o ] = m i n ( a [ e . f r o m ] , e . c a p − e . f l o w ) a[e.to]=min(a[e.from], e.cap-e.flow) a[e.to]=min(a[e.from],e.cape.flow),相当于之前的水的最大和当前的残余容量找一个最小的更新,这是木桶原理(水多了也只能接收我容量内的,水不够我容量的也只有那么多让你接收)。
  • 这样最终到达汇点就说明找到一条增广路了,更新后,利用反向弧来减少由于这次的流量占据的流量,相当于缩小容量
  • 当找不到增广路后显然找到最大流,算法结束

实现

洛谷3376

#include <bits/stdc++.h>
using namespace std;

/**
 * 最大流增广路EK算法
*/

namespace EK {
    const int inf = 0x3f3f3f3f; //给一个最大的水流量
    const int maxnn = (int)1e4+5;

    struct Edge {
        int from, to, cap, flow; //边的起点  边的终点  边的容量  边的流量
        Edge(int a, int b, int c, int d):from(a), to(b), cap(c), flow(d) {}
    };
    
    int n, m; //图的点个数  正向边个数
    vector<Edge> edge; //注:如果用数组要开2倍的空间  因为有反向边
    vector<int> G[maxnn]; //每个起点对应的edge中的边含有的边号
    int a[maxnn]; //从源点到当前点最多能够接收的水流量
    int pre[maxnn]; //每次寻找一条增广路的前驱弧的编号  (反向边使用)

    void init() {
        for (int i = 0; i <= n; i++) G[i].clear();
        edge.clear();
    }

    void add_edge(int u, int v, int cap) {
        edge.push_back(Edge(u, v, cap, 0)); //正向弧
        edge.push_back(Edge(v, u, 0, 0)); //反向弧
        G[u].push_back(edge.size()-2); //索引表
        G[v].push_back(edge.size()-1);
    }

    //类似于spfa优化bellman-ford的思想 只有更新过的边才重新考虑
    int bfs(int s, int t) { 
        int flow = 0; //整个系统的最大流量
        //每次for寻找一条增广路更新系统的流量
        for(;;) {
            memset(a, 0, sizeof(a)); //每次初始化为每个结点都没有流量
            queue<int> q;
            a[s] = inf; //源点含有巨量的水源
            q.push(s);
            while (!q.empty()) {
                int cur = q.front(); q.pop(); //找到一个节点去更新下一个节点
                for (int i = 0; i < G[cur].size(); i++) {
                    int s = G[cur][i]; //弧编号
                    Edge& e = edge[s]; //处理这条弧  如果可以灌水  则要更新为残余流量
                    if (!a[e.to] && e.cap > e.flow) { //如果弧对应的终点没灌水  并且还有残余就要灌水进去
                        a[e.to] = min(e.cap-e.flow, a[cur]); //每次灌进当前路的瓶颈水量
                        pre[e.to] = s; //记录前驱路径
                        q.push(e.to); //加入队列用来下一次更新
                    }
                }
                if (a[t]) break; //发现找到一条增广路马上把这次的流量做统计
            }
            if (!a[t]) break; //发现不存在增广路  即最大流
            //更新最大流
            for (int cur = t; cur != s; cur = edge[pre[cur]].from) { //反向更新残余流量
                edge[pre[cur]].flow += a[t];
                edge[pre[cur]^1].flow -= a[t]; //反向边
            }
            flow += a[t];
        }
        return flow;
    }
}


int main() {
    int n, m, s, t;
    cin >> n >> m >> s >> t;
    EK::n = n;
    EK::m = m;
    EK::init();
    int u, v, cap;
    for (int i = 0; i < m; i++) {
        cin >> u >> v >> cap;
        EK::add_edge(u, v, cap);
    }
    cout << EK::bfs(s, t) << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小胡同的诗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值