题解:洛谷 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
【核心思想】
-
问题分析:给定一个包含 \(n\) 个节点、\(m\) 条边的无向图,需要求出所有点对 \((i, j)\) 之间的最短路径长度。这是一个典型的多源最短路径问题,需要计算图中每一对顶点之间的最短距离。由于数据范围 \(n \le 100\) 相对较小,适合使用基于动态规划的 Floyd 算法一次性求出所有点对的最短路。
-
算法选择:
- Floyd-Warshall 算法:是一种基于动态规划思想的全源最短路算法,可以高效地求出图上任意两点之间的最短路。其核心思想是动态规划,通过逐步引入中间顶点来更新最短路径。Floyd 算法适用于带权有向图或无向图,并且可以处理正权边和负权边(但不能有负权环)。
- 邻接矩阵:使用二维数组
g[N][N]存储图的信息,g[i][j]表示从节点 \(i\) 到节点 \(j\) 的当前最短距离。
-
关键步骤:
- 初始化:将所有节点对之间的距离初始化为无穷大(表示不可达),并将每个节点到自身的距离设为 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\) 的最短路径长度。按矩阵格式输出即可。
- 初始化:将所有节点对之间的距离初始化为无穷大(表示不可达),并将每个节点到自身的距离设为 0。对于输入的每条无向边 \((u, v, w)\),由于可能存在重边,需取最小权值:
-
时间/空间复杂度:
- 时间复杂度:\(O(n^3)\),由于 \(n \le 100\),三重循环的复杂度完全可行。
- 空间复杂度:\(O(n^2)\),用于存储邻接矩阵。
-
动态规划的核心思想:
- 多阶段决策: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
浙公网安备 33010602011771号