德育未来集训笔记-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\) 的最短路径长度,算法的状态转移基于以下核心逻辑:
- 初始时,\(dist[i][j]\) 直接赋值为邻接矩阵的原始值(\(i\) 到 \(j\) 有直接边则为边的权重,\(i = j\) 则为 0,无直接边则为无穷大 \(∞\))。
- 依次将每个顶点 \(k\)(从 1 到 n)作为中间中转点,遍历所有顶点对 \((i, j)\),执行松弛操作:若 \(dist[i][k] + dist[k][j] < dist[i][j]\),则更新 \(dist[i][j] = dist[i][k] + dist[k][j]\)。
- 当所有中间顶点 \(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:核心三层循环(中间顶点松弛迭代)
通过三层嵌套循环完成所有顶点对的最短路径更新,外层循环遍历中间中转点,内层两层循环遍历所有顶点对:
- 外层循环:遍历所有可能的中间中转点 \(k\)(从 1 到 \(n\)),表示当前尝试以 \(k\) 作为 \(i\) 到 \(j\) 的中转点。
- 中层循环:遍历所有可能的起点 \(i\)(从 1 到 \(n\))。
- 内层循环:遍历所有可能的终点 \(j\)(从 1 到 \(n\))。
- 松弛操作:对于当前的顶点对 \((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\le N\le 2.5\times10^2\),数字不超过 C/C++ 的 int 范围。
- 图为无向图,边的权重均为非负整数。
- 若两点间不可达,输出
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;
}
本文来自博客园,作者:jtbg,转载请注明原文链接:https://www.cnblogs.com/jtbg/articles/19468132
博客最新公告
浙公网安备 33010602011771号