题解:洛谷 B3647 Floyd

【题目来源】

洛谷:B3647 【模板】Floyd - 洛谷

【题目描述】

给出一张由 \(n\) 个点 \(m\) 条边组成的无向图。

求出所有点对 \((i,j)\) 之间的最短路径。

【输入】

第一行为两个整数 \(n,m\),分别代表点的个数和边的条数。

接下来 \(m\) 行,每行三个整数 \(u,v,w\),代表 \(u,v\) 之间存在一条边权为 \(w\) 的边。

【输出】

输出 \(n\) 行每行 \(n\) 个整数。

\(i\) 行的第 \(j\) 个整数代表从 \(i\)\(j\) 的最短路径。

【输入样例】

4 4
1 2 1
2 3 1
3 4 1
4 1 1

【输出样例】

0 1 2 1
1 0 1 2
2 1 0 1
1 2 1 0

【核心思想】

  1. 问题分析:给定一个包含 \(n\) 个节点、\(m\) 条边的无向图,需要求出所有点对 \((i, j)\) 之间的最短路径长度。这是一个典型的多源最短路径问题,需要计算图中每一对顶点之间的最短距离。由于数据范围 \(n \le 100\) 相对较小,适合使用基于动态规划的 Floyd 算法一次性求出所有点对的最短路。

  2. 算法选择

    • Floyd-Warshall 算法:是一种基于动态规划思想的全源最短路算法,可以高效地求出图上任意两点之间的最短路。其核心思想是动态规划,通过逐步引入中间顶点来更新最短路径。Floyd 算法适用于带权有向图或无向图,并且可以处理正权边和负权边(但不能有负权环)。
    • 邻接矩阵:使用二维数组 g[N][N] 存储图的信息,g[i][j] 表示从节点 \(i\) 到节点 \(j\) 的当前最短距离。
  3. 关键步骤

    • 初始化:将所有节点对之间的距离初始化为无穷大(表示不可达),并将每个节点到自身的距离设为 0。对于输入的每条无向边 \((u, v, w)\),由于可能存在重边,需取最小权值:g[u][v] = min(g[u][v], w)g[v][u] = min(g[v][u], w)
    • 动态规划转移:使用三重循环,依次考虑每个顶点 \(k\) 作为中间节点。状态转移方程为:
      \(g[i][j] = \min(g[i][j], g[i][k] + g[k][j])\)
      其含义是:从 \(i\)\(j\) 的最短路径,要么不经过顶点 \(k\),要么经过顶点 \(k\)(即路径为 \(i \to k \to j\))。
    • 输出结果:在 \(n\) 轮松弛后,\(g[i][j]\) 存储的就是从 \(i\)\(j\) 的最短路径长度。按矩阵格式输出即可。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^3)\),由于 \(n \le 100\),三重循环的复杂度完全可行。
    • 空间复杂度:\(O(n^2)\),用于存储邻接矩阵。
  5. 动态规划的核心思想

    • 多阶段决策:Floyd 算法的实质是动态规划。设 \(dp[k][i][j]\) 表示从 \(i\)\(j\) 的路径中,除起点和终点外,只允许经过编号不大于 \(k\) 的节点的最短路径长度。
    • 状态转移:考虑是否经过第 \(k\) 个节点。若不经过,则为 \(dp[k-1][i][j]\);若经过,则为 \(dp[k-1][i][k] + dp[k-1][k][j]\)。取两者的最小值。
    • 空间优化:由于 \(dp[k]\) 只依赖于 \(dp[k-1]\),可以使用滚动数组的思想将三维 DP 压缩为二维。在实际代码中,直接在原矩阵上更新即可。这是因为 \(g[i][k]\)\(g[k][j]\) 在当前阶段已经被优化过,直接使用不影响最终结果。
    • 适用场景:Floyd 算法适用于节点数较少(通常 \(n \le 200\))的稠密图,或需要多次查询任意两点间最短路的场景。它是解决多源最短路径问题的经典模板。

【算法标签】

普及 #Floyd

【代码详解】

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

const int N = 105;        // 最大节点数

int n;                     // 节点数量
int m;                     // 边数量
int g[N][N];               // 邻接矩阵,g[i][j]表示节点i到节点j的最短距离

/**
 * Floyd-Warshall算法:计算所有节点对之间的最短路径
 * 动态规划思想:逐步考虑每个节点作为中间节点
 */
void floyd()
{
    // 三重循环:依次考虑每个节点作为中间节点
    for (int k = 1; k <= n; k++)
    {
        // 遍历所有起点
        for (int i = 1; i <= n; i++)
        {
            // 遍历所有终点
            for (int j = 1; j <= n; j++)
            {
                // 状态转移方程:
                // 比较直接路径和通过中间节点k的路径
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
}

int main()
{
    // 输入节点数和边数
    cin >> n >> m;

    // 初始化邻接矩阵为无穷大(表示不可达)
    memset(g, 0x3f, sizeof(g));

    // 对角线初始化为0(节点到自身的距离为0)
    for (int i = 1; i <= n; i++)
    {
        g[i][i] = 0;
    }

    // 输入所有边的信息
    while (m--)
    {
        int u, v, w;  // u:起点, v:终点, w:权重
        cin >> u >> v >> w;

        // 添加无向边(双向权重相同)
        g[u][v] = min(g[u][v], w);  // 取最小权重(处理重边)
        g[v][u] = min(g[v][u], w);  // 无向图需要双向设置
    }

    // 执行Floyd算法计算所有节点对的最短路径
    floyd();

    // 输出结果矩阵
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            // 输出节点i到节点j的最短距离
            cout << g[i][j] << " ";
        }
        cout << endl;  // 换行
    }

    return 0;
}

【运行结果】

4 4
1 2 1
2 3 1
3 4 1
4 1 1
0 1 2 1
1 0 1 2
2 1 0 1
1 2 1 0
posted @ 2026-09-01 16:51  团爸讲算法  阅读(20)  评论(0)    收藏  举报