7-8 弗洛伊德-沃肖算法(全对最短路径)

弗洛伊德-沃肖算法(Floyd-Warshall — All Pairs Shortest Paths)

弗洛伊德-沃肖算法(Floyd-Warshall Algorithm)是一种求解所有节点对之间最短路径(All-Pairs Shortest Paths)的经典算法。其核心思想是动态规划(Dynamic Programming):逐步考虑每个顶点作为中间节点(Intermediate Vertex),检查通过该中间节点是否能缩短任意两个节点之间的距离。

具体来说,设 dist[i][j] 表示从节点 i 到节点 j 的当前最短距离。对于每个中间节点 k(从 0 到 V-1),更新所有节点对 (i, j):

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

如果通过节点 k 中转能获得更短的路径,就更新 dist[i][j]。经过 V 轮迭代后,dist[i][j] 就是节点 i 到节点 j 的最短距离。

时间复杂度 O(V^3),空间复杂度 O(V^2),适合稠密图(Dense Graph)或需要所有节点对最短路径的场景。

本文使用如下加权有向图(4 个顶点):

邻接矩阵(Adjacency Matrix):
     0    1    2    3
0 [  0    3  INF    5 ]
1 [  2    0  INF    4 ]
2 [INF    1    0  INF ]
3 [INF  INF    2    0 ]

INF = 99999 (表示不可达)

图的结构:
    0 --3--> 1
    |        |
    5        4
    |        |
    v        v
    3 --2--> 2 --1--> 1
    (注意: 2→1 权重1, 1→0 权重2)

图的表示(邻接矩阵)

Floyd-Warshall 算法使用邻接矩阵(Adjacency Matrix)来表示图。adj[i][j] 表示从节点 i 到节点 j 的边权重。若 i == j,权重为 0;若 i 到 j 没有直接边,权重设为一个很大的值(INF)表示不可达。

#include <iostream>
#include <vector>
using namespace std;

const int INF = 99999;

int main() {
    // Adjacency matrix for the weighted directed graph
    vector<vector<int>> adj = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };

    int n = adj.size();

    // Print adjacency matrix
    cout << "Adjacency Matrix:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (adj[i][j] == INF)
                cout << "INF\t";
            else
                cout << adj[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}
#include <stdio.h>

#define V 4
#define INF 99999

int main() {
    // Adjacency matrix for the weighted directed graph
    int adj[V][V] = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };

    // Print adjacency matrix
    printf("Adjacency Matrix:\n");
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (adj[i][j] == INF)
                printf("INF\t");
            else
                printf("%d\t", adj[i][j]);
        }
        printf("\n");
    }

    return 0;
}
INF = 99999

def main():
    # Adjacency matrix for the weighted directed graph
    adj = [
        [0,   3,   INF, 5],
        [2,   0,   INF, 4],
        [INF, 1,   0,   INF],
        [INF, INF, 2,   0],
    ]

    # Print adjacency matrix
    print("Adjacency Matrix:")
    for row in adj:
        print("\t".join("INF" if x == INF else str(x) for x in row))

if __name__ == "__main__":
    main()
package main

import "fmt"

const INF = 99999

func main() {
    // Adjacency matrix for the weighted directed graph
    adj := [][]int{
        {0, 3, INF, 5},
        {2, 0, INF, 4},
        {INF, 1, 0, INF},
        {INF, INF, 2, 0},
    }

    // Print adjacency matrix
    fmt.Println("Adjacency Matrix:")
    for _, row := range adj {
        for _, val := range row {
            if val == INF {
                fmt.Print("INF\t")
            } else {
                fmt.Printf("%d\t", val)
            }
        }
        fmt.Println()
    }
}

上述代码构建了示例加权有向图的邻接矩阵。对角线上的值为 0(节点到自身的距离),INF(99999)表示两个节点之间没有直接边。邻接矩阵是 Floyd-Warshall 算法的天然数据结构,因为算法需要频繁地按索引访问任意两个节点之间的距离。

运行该程序将输出:

Adjacency Matrix:
0	3	INF	5
2	0	INF	4
INF	1	0	INF
INF	INF	2	0

Floyd-Warshall 算法实现

Floyd-Warshall 算法的核心是三重循环:

for k = 0 to V-1:          // k is the intermediate vertex
    for i = 0 to V-1:      // i is the source vertex
        for j = 0 to V-1:  // j is the destination vertex
            if dist[i][k] + dist[k][j] < dist[i][j]:
                dist[i][j] = dist[i][k] + dist[k][j]

外层循环遍历每个中间节点 k。内层两层循环检查所有节点对 (i, j),判断通过 k 中转是否能缩短 i 到 j 的距离。

迭代过程跟踪:

初始 dist:
     0    1    2    3
0 [  0    3  INF    5 ]
1 [  2    0  INF    4 ]
2 [INF    1    0  INF ]
3 [INF  INF    2    0 ]

k=0: 检查通过节点 0 中转
  dist[1][2] = min(INF, dist[1][0]+dist[0][2]) = min(INF, 2+INF) = INF
  dist[1][3] = min(4, dist[1][0]+dist[0][3]) = min(4, 2+5) = 4
  dist[2][3] = min(INF, dist[2][0]+dist[0][3]) = min(INF, INF+5) = INF
  ...

k=1: 检查通过节点 1 中转
  dist[0][2] = min(INF, dist[0][1]+dist[1][2]) = min(INF, 3+INF) = INF
  dist[2][0] = min(INF, dist[2][1]+dist[1][0]) = min(INF, 1+2) = 3  ← 更新!
  dist[3][0] = min(INF, dist[3][1]+dist[1][0]) = min(INF, INF+2) = INF
  ...

k=2: 检查通过节点 2 中转
  dist[0][2] = min(INF, dist[0][2]+dist[2][2]) = INF
  dist[1][2] = min(INF, dist[1][2]+dist[2][2]) = INF
  dist[3][0] = min(INF, dist[3][2]+dist[2][0]) = min(INF, 2+3) = 5  ← 更新!
  dist[3][1] = min(INF, dist[3][2]+dist[2][1]) = min(INF, 2+1) = 3  ← 更新!
  ...

k=3: 检查通过节点 3 中转
  dist[0][2] = min(INF, dist[0][3]+dist[3][2]) = min(INF, 5+2) = 7  ← 更新!
  ...

最终 dist:
     0    1    2    3
0 [  0    3    7    5 ]
1 [  2    0    7    4 ]
2 [  3    1    0    5 ]
3 [  5    3    2    0 ]

C++ 实现

#include <iostream>
#include <vector>
using namespace std;

const int INF = 99999;

void floydWarshall(vector<vector<int>>& dist) {
    int n = dist.size();

    // k = intermediate vertex
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                // Skip if either segment is unreachable
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }
}

void printMatrix(const vector<vector<int>>& dist) {
    int n = dist.size();
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (dist[i][j] == INF)
                cout << "INF\t";
            else
                cout << dist[i][j] << "\t";
        }
        cout << endl;
    }
}

int main() {
    vector<vector<int>> dist = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };

    cout << "Initial distances:" << endl;
    printMatrix(dist);

    floydWarshall(dist);

    cout << "\nShortest distances:" << endl;
    printMatrix(dist);

    return 0;
}

C 实现

#include <stdio.h>

#define V 4
#define INF 99999

void floydWarshall(int dist[V][V]) {
    // k = intermediate vertex
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                // Skip if either segment is unreachable
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }
}

void printMatrix(int dist[V][V]) {
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dist[i][j] == INF)
                printf("INF\t");
            else
                printf("%d\t", dist[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int dist[V][V] = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };

    printf("Initial distances:\n");
    printMatrix(dist);

    floydWarshall(dist);

    printf("\nShortest distances:\n");
    printMatrix(dist);

    return 0;
}

Python 实现

INF = 99999

def floyd_warshall(dist):
    """Floyd-Warshall algorithm for all-pairs shortest paths."""
    n = len(dist)

    # k = intermediate vertex
    for k in range(n):
        for i in range(n):
            for j in range(n):
                # Skip if either segment is unreachable
                if dist[i][k] != INF and dist[k][j] != INF:
                    if dist[i][k] + dist[k][j] < dist[i][j]:
                        dist[i][j] = dist[i][k] + dist[k][j]

def print_matrix(dist):
    """Print the distance matrix."""
    for row in dist:
        print("\t".join("INF" if x == INF else str(x) for x in row))

if __name__ == "__main__":
    dist = [
        [0,   3,   INF, 5],
        [2,   0,   INF, 4],
        [INF, 1,   0,   INF],
        [INF, INF, 2,   0],
    ]

    print("Initial distances:")
    print_matrix(dist)

    floyd_warshall(dist)

    print("\nShortest distances:")
    print_matrix(dist)

Go 实现

package main

import "fmt"

const INF = 99999

func floydWarshall(dist [][]int) {
    n := len(dist)

    // k = intermediate vertex
    for k := 0; k < n; k++ {
        for i := 0; i < n; i++ {
            for j := 0; j < n; j++ {
                // Skip if either segment is unreachable
                if dist[i][k] != INF && dist[k][j] != INF {
                    if dist[i][k]+dist[k][j] < dist[i][j] {
                        dist[i][j] = dist[i][k] + dist[k][j]
                    }
                }
            }
        }
    }
}

func printMatrix(dist [][]int) {
    for _, row := range dist {
        for _, val := range row {
            if val == INF {
                fmt.Print("INF\t")
            } else {
                fmt.Printf("%d\t", val)
            }
        }
        fmt.Println()
    }
}

func main() {
    dist := [][]int{
        {0, 3, INF, 5},
        {2, 0, INF, 4},
        {INF, 1, 0, INF},
        {INF, INF, 2, 0},
    }

    fmt.Println("Initial distances:")
    printMatrix(dist)

    floydWarshall(dist)

    fmt.Println("\nShortest distances:")
    printMatrix(dist)
}

上述代码实现了 Floyd-Warshall 算法。核心是三重嵌套循环:最外层的 k 遍历每个中间节点,内层的 i 和 j 遍历所有节点对。对于每个节点对 (i, j),检查是否存在通过 k 中转的更短路径。在更新前会先检查 dist[i][k]dist[k][j] 是否为 INF,避免溢出导致错误。算法直接在输入矩阵上原地修改,不需要额外的矩阵空间。

运行该程序将输出:

Initial distances:
0	3	INF	5
2	0	INF	4
INF	1	0	INF
INF	INF	2	0

Shortest distances:
0	3	7	5
2	0	7	4
3	1	0	5
5	3	2	0

路径还原

Floyd-Warshall 算法不仅计算最短距离,还可以还原具体的路径。为此需要维护一个 next 矩阵:next[i][j] 表示从 i 到 j 的最短路径上,i 的下一个节点。初始化时,如果 i 到 j 有直接边,则 next[i][j] = j,否则为 -1。每当通过中间节点 k 找到更短路径时,更新 next[i][j] = next[i][k]

路径还原时,从起点 i 出发,沿着 next 矩阵逐步跳转,直到到达终点 j。

C++ 实现

#include <iostream>
#include <vector>
using namespace std;

const int INF = 99999;

void floydWarshallWithPath(vector<vector<int>>& dist, vector<vector<int>>& nxt) {
    int n = dist.size();

    // Initialize next matrix for path reconstruction
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (dist[i][j] != INF && i != j) {
                nxt[i][j] = j;  // Direct edge exists: next hop is j
            } else {
                nxt[i][j] = -1; // No direct edge
            }
        }
    }

    // Floyd-Warshall with path tracking
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                        nxt[i][j] = nxt[i][k];  // Reroute through k
                    }
                }
            }
        }
    }
}

void printPath(const vector<vector<int>>& nxt, int src, int dst) {
    if (nxt[src][dst] == -1) {
        cout << "No path from " << src << " to " << dst;
        return;
    }
    cout << src;
    int cur = src;
    while (cur != dst) {
        cur = nxt[cur][dst];
        cout << " -> " << cur;
    }
}

int main() {
    vector<vector<int>> dist = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };
    int n = dist.size();
    vector<vector<int>> nxt(n, vector<int>(n, -1));

    floydWarshallWithPath(dist, nxt);

    // Print all shortest paths
    cout << "Shortest paths:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i != j) {
                cout << "  " << i << " -> " << j
                     << " (distance " << dist[i][j] << "): ";
                printPath(nxt, i, j);
                cout << endl;
            }
        }
    }

    return 0;
}

C 实现

#include <stdio.h>

#define V 4
#define INF 99999

void floydWarshallWithPath(int dist[V][V], int nxt[V][V]) {
    // Initialize next matrix for path reconstruction
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dist[i][j] != INF && i != j) {
                nxt[i][j] = j;  // Direct edge exists
            } else {
                nxt[i][j] = -1; // No direct edge
            }
        }
    }

    // Floyd-Warshall with path tracking
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                        nxt[i][j] = nxt[i][k];  // Reroute through k
                    }
                }
            }
        }
    }
}

void printPath(int nxt[V][V], int src, int dst) {
    if (nxt[src][dst] == -1) {
        printf("No path from %d to %d", src, dst);
        return;
    }
    printf("%d", src);
    int cur = src;
    while (cur != dst) {
        cur = nxt[cur][dst];
        printf(" -> %d", cur);
    }
}

int main() {
    int dist[V][V] = {
        {0,   3,   INF, 5},
        {2,   0,   INF, 4},
        {INF, 1,   0,   INF},
        {INF, INF, 2,   0},
    };
    int nxt[V][V];

    floydWarshallWithPath(dist, nxt);

    // Print all shortest paths
    printf("Shortest paths:\n");
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (i != j) {
                printf("  %d -> %d (distance %d): ", i, j, dist[i][j]);
                printPath(nxt, i, j);
                printf("\n");
            }
        }
    }

    return 0;
}

Python 实现

INF = 99999

def floyd_warshall_with_path(dist):
    """Floyd-Warshall with path reconstruction."""
    n = len(dist)

    # Initialize next matrix for path reconstruction
    nxt = [[-1] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            if dist[i][j] != INF and i != j:
                nxt[i][j] = j  # Direct edge exists

    # Floyd-Warshall with path tracking
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dist[i][k] != INF and dist[k][j] != INF:
                    if dist[i][k] + dist[k][j] < dist[i][j]:
                        dist[i][j] = dist[i][k] + dist[k][j]
                        nxt[i][j] = nxt[i][k]  # Reroute through k

    return nxt

def print_path(nxt, src, dst):
    """Reconstruct and print the path from src to dst."""
    if nxt[src][dst] == -1:
        print(f"No path from {src} to {dst}", end="")
        return
    path = [src]
    cur = src
    while cur != dst:
        cur = nxt[cur][dst]
        path.append(cur)
    print(" -> ".join(map(str, path)), end="")

if __name__ == "__main__":
    dist = [
        [0,   3,   INF, 5],
        [2,   0,   INF, 4],
        [INF, 1,   0,   INF],
        [INF, INF, 2,   0],
    ]

    nxt = floyd_warshall_with_path(dist)

    # Print all shortest paths
    print("Shortest paths:")
    n = len(dist)
    for i in range(n):
        for j in range(n):
            if i != j:
                print(f"  {i} -> {j} (distance {dist[i][j]}): ", end="")
                print_path(nxt, i, j)
                print()

Go 实现

package main

import "fmt"

const INF = 99999

func floydWarshallWithPath(dist [][]int, nxt [][]int) {
    n := len(dist)

    // Initialize next matrix for path reconstruction
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            if dist[i][j] != INF && i != j {
                nxt[i][j] = j // Direct edge exists
            } else {
                nxt[i][j] = -1 // No direct edge
            }
        }
    }

    // Floyd-Warshall with path tracking
    for k := 0; k < n; k++ {
        for i := 0; i < n; i++ {
            for j := 0; j < n; j++ {
                if dist[i][k] != INF && dist[k][j] != INF {
                    if dist[i][k]+dist[k][j] < dist[i][j] {
                        dist[i][j] = dist[i][k] + dist[k][j]
                        nxt[i][j] = nxt[i][k] // Reroute through k
                    }
                }
            }
        }
    }
}

func printPath(nxt [][]int, src, dst int) {
    if nxt[src][dst] == -1 {
        fmt.Printf("No path from %d to %d", src, dst)
        return
    }
    fmt.Print(src)
    cur := src
    for cur != dst {
        cur = nxt[cur][dst]
        fmt.Printf(" -> %d", cur)
    }
}

func main() {
    dist := [][]int{
        {0, 3, INF, 5},
        {2, 0, INF, 4},
        {INF, 1, 0, INF},
        {INF, INF, 2, 0},
    }
    n := len(dist)
    nxt := make([][]int, n)
    for i := range nxt {
        nxt[i] = make([]int, n)
    }

    floydWarshallWithPath(dist, nxt)

    // Print all shortest paths
    fmt.Println("Shortest paths:")
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            if i != j {
                fmt.Printf("  %d -> %d (distance %d): ", i, j, dist[i][j])
                printPath(nxt, i, j)
                fmt.Println()
            }
        }
    }
}

上述代码在 Floyd-Warshall 算法的基础上增加了路径还原功能。next 矩阵记录了每对节点之间最短路径上的第一个跳转节点。初始化时,如果有直接边则 next[i][j] = j;当通过中间节点 k 找到更短路径时,将 next[i][j] 更新为 next[i][k](即从 i 出发先走向 k 方向的第一步)。路径还原时从起点出发,沿 next 矩阵不断跳转直到终点,逐步输出路径上的所有节点。

运行该程序将输出:

Shortest paths:
  0 -> 1 (distance 3): 0 -> 1
  0 -> 2 (distance 7): 0 -> 3 -> 2
  0 -> 3 (distance 5): 0 -> 3
  1 -> 0 (distance 2): 1 -> 0
  1 -> 2 (distance 6): 1 -> 3 -> 2
  1 -> 3 (distance 4): 1 -> 3
  2 -> 0 (distance 3): 2 -> 1 -> 0
  2 -> 1 (distance 1): 2 -> 1
  2 -> 3 (distance 5): 2 -> 1 -> 3
  3 -> 0 (distance 5): 3 -> 2 -> 1 -> 0
  3 -> 1 (distance 3): 3 -> 2 -> 1
  3 -> 2 (distance 2): 3 -> 2

Floyd-Warshall 的性质

下表总结了 Floyd-Warshall 算法的时间和空间复杂度:

指标 复杂度 说明
时间复杂度(Time Complexity) O(V^3) 三重嵌套循环,每层 V 次迭代
空间复杂度(Space Complexity) O(V^2) 邻接矩阵 dist[V][V],可选的 next[V][V] 用于路径还原

其中 V 是顶点数(Vertex Count)。

Floyd-Warshall 的关键性质:

  • 全源最短路径:一次运行即可计算出所有节点对之间的最短距离,这是 Floyd-Warshall 相对于 Dijkstra(需运行 V 次)的核心优势。
  • 支持负权边:Floyd-Warshall 可以正确处理负权边(Negative Weight Edge),但不能处理负权环(Negative Weight Cycle)。如果最终 dist[i][i] < 0,说明存在负权环。
  • 原地更新:算法直接在距离矩阵上修改,不需要额外的矩阵(但路径还原需要 next 矩阵)。
  • 对稠密图高效:当图比较稠密(E 接近 V^2)时,Floyd-Warshall 的 O(V^3) 复杂度与运行 V 次 Dijkstra 的 O(V * (V^2)) 相当,且实现更简单。

Floyd-Warshall 与其他最短路径算法的对比:

算法 适用场景 时间复杂度 备注
Dijkstra 单源,非负权重 O((V+E) log V) 使用优先队列
Bellman-Ford 单源,支持负权边 O(V * E) 可检测负权环
Floyd-Warshall 全源,支持负权边 O(V^3) 适合稠密图

Floyd-Warshall 的典型应用场景:

应用场景 说明
交通网络(Transportation Network) 计算所有城市之间的最短路线
网络路由(Network Routing) 计算路由器之间的最短路径
传递闭包(Transitive Closure) 判断图中任意两节点是否可达
负权环检测 如果 dist[i][i] < 0 则存在负权环
任意两点最短路(Arbitrary Pair Shortest Path) 需要频繁查询任意两节点间的最短距离
posted @ 2026-04-17 07:59  游翔  阅读(21)  评论(0)    收藏  举报