图算法之最大流算法(Maximum Flow)Ford-Fulkerson 算法Edmonds-Karp 算法详细解读

最大流问题(Maximum Flow Problem)是网络流问题中的一个重要类型。其核心问题是:在一个流网络中,找到源点 s 到汇点 t 的最大可能流量。最大流算法有多种实现方式,其中最经典的两种是 Ford-Fulkerson 算法 和其具体实现版本 Edmonds-Karp 算法

最大流问题的基本概念

  • 流网络 :由有向图组成,其中每条边都有一个容量(表示这条边所能承载的最大流量),并且存在一个源点 s 和汇点 t
  • 流量:每条边上的流量不能超过边的容量。
  • 可行流量 :流量必须满足两个条件:
    1. 容量限制:边上的流量不能超过其容量。
    2. 流量守恒:除源点和汇点之外的所有顶点,其流入量必须等于流出量。

最大流问题的目标

找到源点 s 到汇点 t 的最大流量。最大流问题常见的解决方案包括增广路径的概念,寻找一条未满载的路径,将更多的流量从源点推送到汇点。

Ford-Fulkerson 算法

Ford-Fulkerson 是一种增广路径法,用于求解最大流问题。其核心思想是:

  1. 在残留网络中,反复寻找从源点 s 到汇点 t 的增广路径。
  2. 将流量通过增广路径推送,直到没有可以进一步增广的路径为止。

残留网络

  • 残留容量 :对于任意一条边 (u, v),它的残留容量是当前容量减去已使用的流量。即:残留容量 = 容量 - 流量
  • 反向边 :当我们将流量推送到一条边 (u, v) 时,我们可以将相同的流量推回到源点,即引入一条反向边 (v, u)

Ford-Fulkerson 算法步骤

  1. 初始化:将每条边上的初始流量设为 0。
  2. 在残留网络中寻找一条增广路径(使用深度优先搜索 DFS 或广度优先搜索 BFS)。
  3. 找到增广路径后,确定该路径上能够承载的最大流量(即路径上残留容量的最小值)。
  4. 更新残留网络:在增广路径上推送流量,并更新残留容量。
  5. 重复步骤 2,直到不存在增广路径为止。

伪代码

复制代码
function FordFulkerson(Graph, source, sink):
    maxFlow = 0
    residualGraph = Graph

    while exists augmenting path P from source to sink in residualGraph:
        // 找到路径 P 上的最小残留容量(瓶颈)
        flow = minimum capacity on P
        
        // 更新路径上各边的流量
        for each edge (u, v) in P:
            residualGraph[u][v] -= flow
            residualGraph[v][u] += flow  // 更新反向边

        maxFlow += flow

    return maxFlow

Ford-Fulkerson 算法的 Java 实现

java 复制代码
import java.util.Arrays;
import java.util.LinkedList;

public class FordFulkerson {
    // 使用深度优先搜索 (DFS) 查找增广路径
    public boolean dfs(int[][] residualGraph, int source, int sink, int[] parent) {
        boolean[] visited = new boolean[residualGraph.length];
        LinkedList<Integer> stack = new LinkedList<>();
        stack.push(source);
        visited[source] = true;
        parent[source] = -1;

        while (!stack.isEmpty()) {
            int u = stack.pop();
            for (int v = 0; v < residualGraph.length; v++) {
                if (!visited[v] && residualGraph[u][v] > 0) {  // 找到未访问的节点且容量大于 0
                    stack.push(v);
                    parent[v] = u;
                    visited[v] = true;
                    if (v == sink) {
                        return true;  // 找到从 source 到 sink 的路径
                    }
                }
            }
        }

        return false;
    }

    // Ford-Fulkerson 算法实现
    public int fordFulkerson(int[][] graph, int source, int sink) {
        int u, v;
        int[][] residualGraph = new int[graph.length][graph.length];
        for (u = 0; u < graph.length; u++) {
            for (v = 0; v < graph.length; v++) {
                residualGraph[u][v] = graph[u][v];
            }
        }

        int[] parent = new int[graph.length];
        int maxFlow = 0;

        while (dfs(residualGraph, source, sink, parent)) {
            int pathFlow = Integer.MAX_VALUE;

            // 找到增广路径上的最小残留容量
            for (v = sink; v != source; v = parent[v]) {
                u = parent[v];
                pathFlow = Math.min(pathFlow, residualGraph[u][v]);
            }

            // 更新残留网络
            for (v = sink; v != source; v = parent[v]) {
                u = parent[v];
                residualGraph[u][v] -= pathFlow;
                residualGraph[v][u] += pathFlow;  // 更新反向边
            }

            maxFlow += pathFlow;
        }

        return maxFlow;
    }

    public static void main(String[] args) {
        int graph[][] = new int[][]{
            {0, 16, 13, 0, 0, 0},
            {0, 0, 10, 12, 0, 0},
            {0, 4, 0, 0, 14, 0},
            {0, 0, 9, 0, 0, 20},
            {0, 0, 0, 7, 0, 4},
            {0, 0, 0, 0, 0, 0}
        };
        FordFulkerson maxFlow = new FordFulkerson();
        System.out.println("最大流量: " +
            maxFlow.fordFulkerson(graph, 0, 5));
    }
}

Ford-Fulkerson 算法复杂度分析

  • 时间复杂度:取决于寻找增广路径的方式。如果使用 DFS,那么复杂度为 O(E×F),其中 E 是边数,F 是最大流量。如果流量是大的浮点数,算法可能陷入无限循环,因此通常限制流量为整数。
  • 空间复杂度:O(V2),需要额外的空间来存储残留网络。

Edmonds-Karp 算法

Edmonds-Karp 是 Ford-Fulkerson 算法的一个具体实现版本,它使用广度优先搜索(BFS)来查找增广路径。相比于 Ford-Fulkerson 算法中常用的深度优先搜索,广度优先搜索可以保证找到的是路径中最短的增广路径,并且使得算法在每次增广后流量增加得更快,从而提升了效率。

Edmonds-Karp 算法步骤

  1. 使用广度优先搜索(BFS)查找增广路径。
  2. 通过找到的增广路径更新残留网络。
  3. 重复上述步骤,直到无法找到增广路径。

伪代码

java 复制代码
function EdmondsKarp(Graph, source, sink):
    maxFlow = 0
    residualGraph = Graph

    while exists augmenting path P from source to sink in residualGraph using BFS:
        // 找到路径 P 上的最小残留容量(瓶颈)
        flow = minimum capacity on P
        
        // 更新路径上各边的流量
        for each edge (u, v) in P:
            residualGraph[u][v] -= flow
            residualGraph[v][u] += flow  // 更新反向边

        maxFlow += flow

    return maxFlow

Edmonds-Karp 算法的 Java 实现

java 复制代码
import java.util.*;

public class EdmondsKarp {
    private static final int V = 6; // 顶点数量

    // BFS 寻找增广路径
    private boolean bfs(int[][] rGraph, int s, int t, int[] parent) {
        boolean[] visited = new boolean[V];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(s);
        visited[s] = true;

        while (!queue.isEmpty()) {
            int u = queue.poll();

            for (int v = 0; v < V; v++) {
                if (!visited[v] && rGraph[u][v] > 0) { // 如果尚未访问且有剩余容量
                    if (v == t) { // 如果到达汇点
                        parent[v] = u;
                        return true;
                    }
                    queue.add(v);
                    visited[v] = true;
                    parent[v] = u;
                }
            }
        }
        return false;
    }

    // Edmonds-Karp 算法实现
    public int edmondsKarp(int[][] graph, int s, int t) {
        int u, v;

        // 创建残量图
        int[][] rGraph = new int[V][V];
        for (u = 0; u < V; u++) {
            for (v = 0; v < V; v++) {
                rGraph[u][v] = graph[u][v];
            }
        }

        int[] parent = new int[V]; // 存储增广路径
        int maxFlow = 0; // 初始化最大流

        // 增广路径循环
        while (bfs(rGraph, s, t, parent)) {
            // 找到增广路径的最小容量
            int pathFlow = Integer.MAX_VALUE;
            for (v = t; v != s; v = parent[v]) {
                u = parent[v];
                pathFlow = Math.min(pathFlow, rGraph[u][v]);
            }

            // 更新残量图
            for (v = t; v != s; v = parent[v]) {
                u = parent[v];
                rGraph[u][v] -= pathFlow;
                rGraph[v][u] += pathFlow;
            }

            maxFlow += pathFlow; // 更新最大流
        }

        return maxFlow;
    }

    public static void main(String[] args) {
        int[][] graph = new int[][] {
            {0, 16, 13, 0, 0, 0},
            {0, 0, 10, 12, 0, 0},
            {0, 4, 0, 0, 14, 0},
            {0, 0, 9, 0, 0, 20},
            {0, 0, 0, 7, 0, 4},
            {0, 0, 0, 0, 0, 0}
        };

        EdmondsKarp ek = new EdmondsKarp();
        System.out.println("最大流: " + ek.edmondsKarp(graph, 0, 5)); // 从源点 0 到汇点 5
    }
}

总结

  • Ford-Fulkerson 算法是最大流问题的基础方法,适合理解增广路径和流量更新的过程,但其复杂度依赖于增广路径的查找方法。
  • Edmonds-Karp 算法利用 BFS 查找增广路径,确保了时间复杂度的多项式限制,适合大多数实际应用。

这两种算法在网络流问题的求解中具有重要意义,是计算机科学和运筹学中的经典问题与方法。

相关推荐
阿豪学编程2 小时前
LeetCode724.:寻找数组的中心下标
算法·leetcode
墨韵流芳2 小时前
CCF-CSP第41次认证第三题——进程通信
c++·人工智能·算法·机器学习·csp·ccf
csdn_aspnet3 小时前
C# 求n边凸多边形的对角线数量(Find number of diagonals in n sided convex polygon)
开发语言·算法·c#
凌波粒3 小时前
LeetCode--349.两个数组的交集(哈希表)
java·算法·leetcode·散列表
paeamecium5 小时前
【PAT甲级真题】- Student List for Course (25)
数据结构·c++·算法·list·pat考试
Book思议-5 小时前
【数据结构】栈与队列全方位对比 + C 语言完整实现
c语言·数据结构·算法··队列
SteveSenna5 小时前
项目:Trossen Arm MuJoCo
人工智能·学习·算法
NAGNIP5 小时前
一文搞懂CNN经典架构-DenseNet!
算法·面试
道法自然|~5 小时前
BugCTF黄道十二宫
算法·密码学
WHS-_-20226 小时前
Python 算法题学习笔记一
python·学习·算法