德育未来集训笔记-Day 2-Floyd

Floyd 弗洛伊德

算法简介

Floyd (弗洛伊德)算法(也称为 Floyd-Warshall 算法)是一种经典的全源最短路径算法,用于计算图中所有顶点对之间的最短路径长度。
主要特点是基于动态规划思想,通过一个三维状态的压缩优化,利用邻接矩阵进行层层迭代更新,无需区分源点,一次性求解所有顶点间的最短路径。
Floyd 算法实现简单、逻辑清晰,能处理带权有向图和无向图(无负权环即可,可处理负权边),但由于其时间复杂度较高(\(O(n^3)\)\(n\) 为顶点数),更适合处理小规模图的全源最短路径问题。

基本思想

其基本思想是基于动态规划中间顶点松弛,核心思路是:对于任意两个顶点 \(i\)\(j\),尝试以图中所有顶点 \(k\) 作为中间中转点,判断“\(i \to k \to j\)”的路径长度是否比当前已知的“\(i \to j\)”的路径长度更短,若更短则更新两者之间的最短路径长度。
具体来说,定义二维数组 \(dist[i][j]\) 表示顶点 \(i\) 到顶点 \(j\) 的最短路径长度,算法的状态转移基于以下核心逻辑:

  1. 初始时,\(dist[i][j]\) 直接赋值为邻接矩阵的原始值(\(i\)\(j\) 有直接边则为边的权重,\(i = j\) 则为 0,无直接边则为无穷大 \(∞\))。
  2. 依次将每个顶点 \(k\)(从 1 到 n)作为中间中转点,遍历所有顶点对 \((i, j)\),执行松弛操作:若 \(dist[i][k] + dist[k][j] < dist[i][j]\),则更新 \(dist[i][j] = dist[i][k] + dist[k][j]\)
  3. 当所有中间顶点 \(k\) 都完成迭代后,\(dist[i][j]\) 即为顶点 \(i\) 到顶点 \(j\) 的最终最短路径长度。

简单来说,Floyd 算法的本质是“通过不断引入中间顶点,逐步优化任意两点间的最短路径”。

操作步骤

步骤 1:初始化基础数据结构

定义并初始化邻接矩阵(距离矩阵) \(dist[n+1][n+1]\)\(n\) 为顶点总数,下标通常从 1 开始方便对应顶点编号):

  • 对于任意顶点 \(i\)\(dist[i][i] = 0\)(顶点到自身的最短路径长度为 0)。
  • 若顶点 \(i\) 和顶点 \(j\) 之间有直接相连的边,且边的权重为 \(w\),则 \(dist[i][j] = w\)(无向图需同时赋值 \(dist[j][i] = w\))。
  • 若顶点 \(i\) 和顶点 \(j\) 之间无直接相连的边,则 \(dist[i][j] = ∞\)(表示初始时两点间无可达路径)。

可选定义前驱矩阵 \(prev[n+1][n+1]\):用于回溯任意两个顶点间的具体最短路径,\(prev[i][j]\) 记录顶点 \(i\) 到顶点 \(j\) 的最短路径中,\(j\) 的前一个顶点,初始时 \(prev[i][j] = i\)(若 \(i\)\(j\) 有直接边)或 \(-1\)(无直接边)。

步骤 2:核心三层循环(中间顶点松弛迭代)

通过三层嵌套循环完成所有顶点对的最短路径更新,外层循环遍历中间中转点,内层两层循环遍历所有顶点对:

  1. 外层循环:遍历所有可能的中间中转点 \(k\)(从 1 到 \(n\)),表示当前尝试以 \(k\) 作为 \(i\)\(j\) 的中转点。
  2. 中层循环:遍历所有可能的起点 \(i\)(从 1 到 \(n\))。
  3. 内层循环:遍历所有可能的终点 \(j\)(从 1 到 \(n\))。
  4. 松弛操作:对于当前的顶点对 \((i, j)\),判断经过中转点 \(k\) 的路径是否更短,即满足 \(dist[i][k] + dist[k][j] < dist[i][j]\),且 \(dist[i][k]\)\(dist[k][j]\) 均不为无穷大(避免不可达路径的无效更新)。若满足条件,则更新 \(dist[i][j] = dist[i][k] + dist[k][j]\),同时更新前驱矩阵 \(prev[i][j] = prev[k][j]\)(可选,用于路径回溯)。

步骤 3:输出结果

迭代完成后,距离矩阵 \(dist\) 中存储了所有顶点对之间的最短路径长度:

  • 若要查询顶点 \(a\) 到顶点 \(b\) 的最短路径长度,直接获取 \(dist[a][b]\) 即可。
  • \(dist[a][b] = ∞\),表示顶点 \(a\) 无法到达顶点 \(b\)
  • 若需要具体路径,可通过前驱矩阵 \(prev\) 从终点 \(b\) 倒推回起点 \(a\),还原完整的最短路径。

例1

【题目描述】

输入一个图,求出所有顶点对之间的最短路径长度,按指定格式输出每个顶点到其他所有顶点的距离。

【输入】

第一行一个整数 \(N\) 表示顶点的个数;
接下来依次输入每个顶点的边信息,格式为:

  • 每行先输入两个数 \(x\),\(y\),表示顶点编号 \(x\) 和该顶点连接的边数 \(y\)
  • 接下来 \(y\) 行输入两个数 \(a\),\(b\),表示顶点 \(x\) 有一条边连接顶点 \(a\),且该边的长度(权重)为 \(b\)

【输出】

输出共 \(N\) 段,每段对应一个顶点 \(i\)(从 1 到 \(N\));
每段内输出 \(N\) 行,每行格式为 i:j:dist,表示顶点 \(i\) 到顶点 \(j\) 的最短路径长度;
段与段之间用一行分隔线 --- 隔开(最后一段后无需输出分隔线)。

【输入样例】

7
1 2
2 3
3 4
2 4
7 10
4 6
3 5
1 3
3 4
1 4
2 5
4 2
5 8
4 5
7 7
2 6
3 2
5 9
6 16
5 3
6 14
4 9
3 8
6 3
7 12
4 16
5 14
7 3
6 12
2 10
4 7

【输出样例】

1:1:0
1:2:3
1:3:4
1:4:6
1:5:12
1:6:22
1:7:13
---
2:1:3
2:2:0
2:3:1
2:4:2
2:5:8
2:6:5
2:7:10
---
3:1:4
3:2:1
3:3:0
3:4:3
3:5:9
3:6:6
3:7:11
---
4:1:6
4:2:2
4:3:3
4:4:0
4:5:7
4:6:5
4:7:10
---
5:1:12
5:2:8
5:3:9
5:4:7
5:5:0
5:6:12
5:7:7
---
6:1:22
6:2:5
6:3:6
6:4:5
6:5:12
6:6:0
6:7:12
---
7:1:13
7:2:10
7:3:11
7:4:10
7:5:7
7:6:12
7:7:0

【提示】

  1. \(1\le N\le 2.5\times10^2\),数字不超过 C/C++ 的 int 范围。
  2. 图为无向图,边的权重均为非负整数。
  3. 若两点间不可达,输出 NaN 表示距离。

答案

#include <bits/stdc++.h>
using namespace std;

const int N = 250;       // 最大顶点数
const int INF = 0x3f3f3f3f; // 表示无穷大(避免溢出,可被int存储)
int dist[N][N];          // 距离矩阵,dist[i][j]表示i到j的最短路径长度
int n;                   // n:顶点总数

int main() 
{
    // 1. 初始化距离矩阵为无穷大(初始时两点间无可达路径)
    memset(dist, 0x3f, sizeof(dist));
    for (int i = 1; i <= N; i++) 
    {
        dist[i][i] = 0;  // 顶点到自身的距离为0
    }
    
    // 2. 输入顶点总数
    cin >> n;
    
    // 3. 输入边的信息,构建初始邻接矩阵(无向图)
    for (int i = 1; i <= n; i++) 
    {
        int x, y;
        cin >> x >> y; // x:当前顶点,y:x的出边数
        while (y--) 
        {
            int a, b;
            cin >> a >> b; // a:邻接顶点,b:边权
            dist[x][a] = b; // 无向图,双向赋值
            dist[a][x] = b;
        }
    }
    
    // 4. Floyd 算法核心三层循环:中间顶点k,起点i,终点j
    for (int k = 1; k <= n; k++) // 外层:遍历所有中间中转点k
    {
        for (int i = 1; i <= n; i++) // 中层:遍历所有起点i
        {
            for (int j = 1; j <= n; j++) // 内层:遍历所有终点j
            {
                // 松弛操作:判断经过k中转的路径是否更短,且避免不可达路径的无效更新
                if (dist[i][k] != INF && dist[k][j] != INF) 
                {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
    
    // 5. 输出结果:所有顶点对之间的最短路径长度
    for (int i = 1; i <= n; i++) 
    {
        for (int j = 1; j <= n; j++) 
        {
            cout << i << ":" << j << ":";
            if (dist[i][j] == INF) 
            {
                cout << "NaN" << endl; // 不可到达输出NaN
            } 
            else 
            {
                cout << dist[i][j] << endl;
            }
        }
        // 段与段之间输出分隔线,最后一段不输出
        if (i != n) 
        {
            cout << "---" << endl;
        }
    }
    
    return 0;
}
posted @ 2026-01-11 13:17  jtbg  阅读(2)  评论(0)    收藏  举报