图算法之最大流算法(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 查找增广路径,确保了时间复杂度的多项式限制,适合大多数实际应用。

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

相关推荐
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_4 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子5 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin5 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码5 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7245 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活6 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学6 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习