Floyd-Warshall算法 (Floyd-Warshall Algorithm)

🧮 Floyd-Warshall算法 (Floyd-Warshall Algorithm)

难度 / Difficulty: 困难 (Hard)
分类 / Category: 最短路径 / 动态规划 (Shortest Path / Dynamic Programming)
时间复杂度 / Time Complexity: O(V³)
空间复杂度 / Space Complexity: O(V²)


📖 算法简介 / Introduction

Floyd-Warshall 算法是经典的动态规划算法,用于计算所有节点对之间的最短路径,适用于有向加权图(可含负权边,但不能有负环)。

Floyd-Warshall is a classic dynamic programming algorithm that computes shortest paths between all pairs of vertices in a directed weighted graph. It handles negative edge weights but not negative cycles.


💡 算法原理 / Principle

核心思想:对每一对节点 (i, j),尝试经过每个中间节点 k 是否能缩短路径。

核心公式:

dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

逐步增加允许的中间节点集合,从只允许节点1,到允许节点1、2……直到允许全部节点。

三维视角:令 dp[k][i][j] 表示仅使用前 k 个节点(1 到 k)作为中转时,i 到 j 的最短路径。

转移方程:

dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])

滚动数组优化后只需二维数组,遍历 k 时逐步更新。

Core idea: For each pair (i, j), try whether going through intermediate node k produces a shorter path.

The algorithm iteratively refines the shortest path estimate by considering each vertex as a possible intermediate point. Initially, only direct edges are considered; then one by one, we allow paths to use additional vertices as intermediate nodes.


📝 代码实现 / Implementation

Python 实现(详细注释版)

def floyd_warshall(n, edges):
    """
    Floyd-Warshall 算法求所有节点对最短路径

    参数:
        n: 节点数量(节点编号 0 到 n-1)
        edges: 边列表,每条边为 (u, v, w),表示 u -> v 权重为 w

    返回:
        dist: n x n 距离矩阵,dist[i][j] 表示 i 到 j 的最短距离
    """
    INF = float('inf')
    # 初始化距离矩阵
    dist = [[INF] * n for _ in range(n)]

    # 自身到自身距离为 0
    for i in range(n):
        dist[i][i] = 0

    # 读取所有直接边
    for u, v, w in edges:
        dist[u][v] = w  # 有向图,只更新一个方向

    # 核心:动态规划更新
    # k 是中间节点,尝试通过 k 中转是否能缩短 i 到 j 的路径
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist


# ============ 示例测试 ============
if __name__ == "__main__":
    # 构造一个简单的有向加权图
    # 节点: 0, 1, 2, 3
    #
    #       2 ← 0 → 3
    #       ↑   ↓   ↑
    #       1 ← — +

    n = 4
    edges = [
        (0, 1, 5),   # 0 -> 1,权重 5
        (0, 2, 3),   # 0 -> 2,权重 3
        (1, 2, -2),  # 1 -> 2,权重 -2(负权边)
        (1, 3, 6),   # 1 -> 3,权重 6
        (2, 3, 2),   # 2 -> 3,权重 2
    ]

    dist = floyd_warshall(n, edges)

    print("所有节点对最短距离矩阵:")
    print("    0    1    2    3")
    for i in range(n):
        row = [f"{dist[i][j]:4}" for j in range(n)]
        print(f"{i}: {' '.join(row)}")

    print(f"\n0 → 3 的最短距离: {dist[0][3]}")  # 期望: 5 (0→2→1→3 路径)

运行结果:

所有节点对最短距离矩阵:
    0    1    2    3
0:   0    5    3    5
1: inf   0   -2    0
2: inf inf   0    2
3: inf inf inf   0

0 → 3 的最短距离: 5

Java 实现(面试白板版)

public class FloydWarshall {
    public static int[][] floydWarshall(int n, int[][] edges) {
        final int INF = Integer.MAX_VALUE;
        int[][] dist = new int[n][n];

        // 初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = (i == j) ? 0 : INF;
            }
        }

        // 读边
        for (int[] e : edges) {
            int u = e[0], v = e[1], w = e[2];
            dist[u][v] = w;
        }

        // 核心动态规划
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                if (dist[i][k] == INF) continue;  // 剪枝优化
                for (int j = 0; j < n; j++) {
                    if (dist[k][j] == INF) continue;  // 剪枝优化
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }

        return dist;
    }

    public static void main(String[] args) {
        int n = 4;
        int[][] edges = {
            {0, 1, 5}, {0, 2, 3},
            {1, 2, -2}, {1, 3, 6},
            {2, 3, 2}
        };

        int[][] dist = floydWarshall(n, edges);
        System.out.println("0 → 3 = " + dist[0][3]);  // 输出: 5
    }
}

✨ 示例解析 / Example Analysis

问题: 4节点有向图,边如下:

0 → 1 (权重 5)    1 → 3 (权重 6)
0 → 2 (权重 3)    2 → 3 (权重 2)
1 → 2 (权重 -2)

手动推导 0 → 3 的最短路径:

迭代 k 允许中转节点 0→3 距离
初始 0→2→3 = 3+2 = 5
k=0 {0} 无改进
k=1 {0,1} 0→1→2→3 = 5+(-2)+2 = 5(0→3不变)
k=2 {0,1,2} 无改进(已经用了2)
k=3 {0,1,2,3} 无改进

最终答案是 5。最短路径可能是 0→2→30→1→2→3


🔍 算法对比 / Comparison

特性 Floyd-Warshall Dijkstra (单源)
时间复杂度 O(V³) O((V+E) log V)
适用场景 所有节点对最短路径 单源最短路径
负权边 ✅ 支持 ❌ 不支持(需Bellman-Ford)
负环检测 ✅ 可检测 ❌ 做不到
空间复杂度 O(V²) O(V)

当需要多源最短路径时,Dijkstra 需要运行 V 次,总复杂度 O(V³ log V),与 Floyd-Warshall 相同,但 Floyd-Warshall 实现更简洁,且天然支持负权边。


🎯 适用场景 / Scenarios

  • 路由算法:计算网络中所有节点对的最短路径(如 OSPF 内部网关协议)
  • 传递闭包:检测图中任意两点是否可达(Floyd-Warshall 的布尔版本)
  • 负环检测:用于检测金融网络中是否存在套利机会(Bellman-Ford的变体)
  • 近似算法:作为下界,用于旅行商问题(TSP)的启发式求解
  • 多源多汇网络流:需要知道任意两点间的距离来优化流分配

⚠️ 注意事项 / Caveats

  1. 不能处理负环:如果图中存在负环,最短路径无定义(Floyd-Warshall 会不断更新导致错误结果)。可额外检测对角线上的负值来检测负环。

  2. 稠密图效率高:当图稠密时(F² ≈ V²),Floyd-Warshall 的三循环结构实际运行很快;而稀疏图用 Dijkstra + 堆更优。

  3. 路径重构:实际应用中往往需要记录前驱节点 next[i][j]next[i][j],在更新时同步记录,复杂度仅加 O(1)。


🔄 扩展阅读 / Further Reading

  • 负环检测:在算法结束后检查 dist[i][i] < 0 的节点,即可发现负环
  • 路径重构:使用 next[i][j] 记录从 i 到 j 的下一步,完整路径可递归回溯
  • 优化方向:对于极大图,可采用「分治 Floyd-Warshall」或「基于排序的算法」
  • 对比学习:建议同时学习 Dijkstra(单源)、Bellman-Ford(单源+负权)、SPFA(队列优化版 Bellman-Ford)

📚 参考资料 / References

  • Cormen, Leiserson, Rivest, Stein. Introduction to Algorithms (CLRS), Chapter 25
  • 维基百科: Floyd-Warshall algorithm

*本文由 AI 自动生成 Generated by AI*

📌 隐私说明:网站使用 Google AdSense 推送相关广告。Google 可能使用 Cookie 进行访客分析。

📌 Privacy Notice: This site uses Google AdSense to serve relevant ads. Google may use cookies for visitor analytics.