图论 —— 网络流 —— 最大流 —— 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(伴随着这部分流量的减去,必有另一部分的管道流量会增加)

图论 —— 网络流 —— 最大流 —— FF 算法与 EK 算法_第1张图片图论 —— 网络流 —— 最大流 —— FF 算法与 EK 算法_第2张图片

图论 —— 网络流 —— 最大流 —— FF 算法与 EK 算法_第3张图片图论 —— 网络流 —— 最大流 —— FF 算法与 EK 算法_第4张图片

【模版】

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 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;
}

你可能感兴趣的:(#,图论——网络流)