图论 —— 网络流 —— 最大流 —— FF 算法与 EK 算法

【概述】

FF 算法与 EK 算法是求解最大流的一般增广路方法,其时间复杂度均为 O(n*m*m)

Ford-Fulkerson 算法是求解最大流的最基础的算法,其核心思想是增广路定理:网络达到最大流当且仅当残留网络中没有增广路

程序的实现过程与增广路求最大流的过程基本一致,即每一次更新都进行一次找增广路然后更新路径上的流量的过程。

在传统的 FF 算法中,利用 dfs 每次找增广路的过程十分繁琐,常常会走冤枉路,此时更新增广路的复杂度就会增加,EK 算法为了规避这个问题使用了 bfs 来寻找增广路,然后在寻找增广路的时候总是向离汇点越来越近的方向去寻找下一个结点。

【基本思想】

1)若存在增广路径,则找出一条增广路径(通过 BFS)

2)沿着找出的增广路径进行更新流量

3)当没有增广路时,网络达到最大流

【沿增广路径增广方法】

第一步:计算可增加流量

  设:某一增广路径结点为 a1,a2,...,an,可增加流的增加流量 dis=INF

    若 (u,v) 是正向边,则:dis=min(dis,c(ai,aj)-f(ai,aj)),其中:j=i+1,i=1,2,...,n-1

    若 (u,v) 是逆向边,则:dis=min(dis,f(ai,aj)),其中:j=i+1,i=1,2,...,n-1

第二步:更新流量

  若 (u,v) 是正向边,则:f(u,v)=f(u,v)+dis

  若 (u,v) 是负向边,则:f(u,v)=f(u,v)-dis(伴随着这部分流量的减去,必有另一部分的管道流量会增加)

【模版】

1.FF 算法

struct Edge {
    int to, next;
    int cap;
} edge[N * N];
int head[N], tot;
bool vis[N], flag;
LL res;
void addEdge(int x, int y, int cap) {
    edge[tot].to = y;
    edge[tot].cap = cap;
    edge[tot].next = head[x];
    head[x] = tot++;

    edge[tot].to = x;
    edge[tot].cap = 0;
    edge[tot].next = head[y];
    head[y] = tot++;
}
int dfs(int x, int T, int flow) { // dfs求任意路径
    if (x == T) {
        res += flow;
        flag = true;
        return flow;
    }

    vis[x] = true;
    for (int i = head[i]; i != -1; i = edge[i].next) {
        int x1 = edge[i].to;
        if (vis[x1] || edge[i].cap == 0)
            continue;
        int newFlow = dfs(x1, T, min(flow, edge[i].cap));
        if (flag) {
            edge[i].cap -= newFlow;
            edge[i ^ 1].cap += newFlow;
            return newFlow;
        }
    }
    return 0;
}
void FF(int S, int T) { //有增广路就增广
    flag = 0;
    memset(vis, 0, sizeof(vis));
    dfs(S, T, INF);

    while (flag) {
        flag = 0;
        memset(vis, 0, sizeof(vis));
        dfs(S, T, INF);
    }
}
int main() {
    memset(head, -1, sizeof(head));
    tot = 0;
    res = 0;

    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y, cap;
        scanf("%d%d%d", &x, &y, &cap);
        addEdge(x, y, cap);
    }
    int S = 1, T = n;
    FF(S, T);
    printf("%d\n", res);

    return 0;
}

2.EK 算法

int n, m;
int cap[N][N];  //容量
int flow[N][N]; //流量

int EK(int s, int t) { //沿着增广路增广
    int res = 0;       //最大流
    int dis[N];        // a[i]表示从s到i的最小残量
    int p[N];          //增广路中的上一节点
    queue<int> Q;
    while (true) {
        memset(dis, 0, sizeof(dis));
        dis[s] = INF;
        Q.push(s);
        //计算可增加流量
        while (!Q.empty()) {
            int x = Q.front();
            Q.pop();
            for (int y = 1; y <= n; y++) {
                if (!dis[y] && cap[x][y] > flow[x][y]) {
                    p[y] = x;
                    Q.push(y);
                    dis[y] = min(dis[x], cap[x][y] - flow[x][y]);
                }
            }
        }

        if (dis[t] == 0) //当网络中没有增广路径
            break;
        //更新流量
        for (int x = t; x != s; x = p[x]) {
            flow[p[x]][x] += dis[t];
            flow[x][p[x]] -= dis[t];
        }
        res += dis[t];
    }
    return res; //返回最大流
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    memset(cap, 0, sizeof(cap));
    memset(flow, 0, sizeof(flow));
    while (m--) {
        int x, y, w;
        scanf("%d%d%d", &x, &y, &w); //两点的容量
        cap[x][y] = +w;              //可能有重边
    }
    printf("%d\n", EK(1, n));
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值