Floyd 算法
参考:
《数据结构教程》 李春葆主编 (第五版)
《ACM/ICPC 算法训练教程》徐立功主编
https://baike.baidu.com/item/Floyd%E7%AE%97%E6%B3%95/291990?fr=aladdin
一,概念
求解 APSP(All Paris Shortest Paths)问题,即求任意点到其他点的最短路径。时间复杂度 是 O(n的3次方),边权可正可负,唯一要求是不能有负环。
二,原理
如果能保证边是非负的,那么长度长的最短路径一定是在长度短的最短路径的基础上延伸出来的
因为:
假设 a 到 c 的最短路经过 b,显然 a 至 c 这条最短路必须由 a 至 b 的最短路和 b 至 c 的最短路组合起来才是最短的。(反证法很容易证明,这里就不给出了)
三,算法推导(动态规划)
1,三维表示
设状态数组 f[ k ][ i ][ j ] 表示:
在只考虑前 k 个点的情况下,点 i 到点 j 的最短路径值
由上述原理可得状态递推式:
f[ k ][ i ] [ j ] = min( f[ k-1 ][ i ][ j ], f[ k-1 ][ i ][ k] + f[ k-1 ][ k ][ j ] )
2,二维表示(状态压缩)
可以发现,每个 k 只与 k-1 有关,所以可以将三维数组压缩成二维,用二维的滚动数组来表示
设 f[ i ][ j ] 表示:
在只考虑前 k 个点的情况下,点 i 到点 j 的最短路径值。其中,k 没有在数组中表示出来
对于每次 k 的循环,
在 f 更新之前,f 只能表示 f[ k-1 ][ i ][ j ]。
在 f 更新之后,f 只能表示 f[ k ][ i ][ j ]。
对于 k 的每次循环, f 的值都会被更新,所以 f 是无法存储 k-1 之前的状态的,所以称之为滚动数组
所以有推导式:
f[ i ] [ j ] = min( f[ i ][ j ], f[ i ][ k] + f[ k ][ j ] )
四,步骤
1,初始化
初始化状态数组,若存在 <vi, vj>, 则为其权值,否则为无穷大
2,试探
尝试在所有路径中加入中间顶点 Vk,若距离变短,则修改状态数组和路径数组,否则不变
3,循环
试探所有顶点
五,记录路径的方法
1,使用二维数组 p 记录路径,其中
p[ i ][ j ] 表示 Vi 到 Vj 的最短路径上,Vj 的前一个结点
2,使用步骤
① 初始化:若存在 <vi, vj>, 则 p[ i ][ j ] = i,否则 p[ i ][ j ] = -1
② 更新:若考虑 Vk,则 Vi 到 Vj 的路径更短,则 p[ i ][ j ] = k
③ 输出:假设我们要找 A->B 的最短路径,那么就根据 p 从 B 往前查找,直至找到 A
先查找 p[ a ][ b ],得 p[ a ][ b ] = c
再查找 p[ a ][ c ],得 p[ a ][ c ] = d
再查找 p[ a ][ d ],得 p[ a ][ d ] = a
发现,a 代表起始点 A,所以查找结束,最短路径为 a->d->c->b
该过程可以通过递归回溯输出实现,即从终点 B 搜索到起点 A,再回溯输出即可。(由于 p 是代表前一个结点,所以输出的时候并没有输出终点 )
其中,要注意如果在搜索过程中,发现存在某次 p[ i ][ j ] == -1,说明起点与终点并没有连通
六,代码
三维:(注意点:除了距离变短的值更新,距离不变的值也要从 k-1 继承到 k )
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 110 #define MIN(x, y) (x<y?x:y) int f[N][N][N]; // 状态数组 int p[N][N]; // p[i][j]: Vi 到 Vj 的最短路径上,Vj 的前一个结点 int n, m; void find(int i, int j) { if (p[i][j] == -1) { printf("该两点并不连通"); return; } if (p[i][j] != i) find(i, p[i][j]); else printf("%d", i); printf("->%d", j); } void show() // 输出所有最短路径 { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) continue; printf("点 %d 到 点 %d 的最短路径值:%d,最短路径:", i, j, f[n][i][j]); find(i, j); puts(""); } } } void floyd() { for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if (f[k - 1][i][k] + f[k - 1][k][j] < f[k - 1][i][j]) p[i][j] = p[k][j]; f[k][i][j] = MIN(f[k - 1][i][k] + f[k - 1][k][j], f[k - 1][i][j]); } } int main(void) { while (scanf("%d%d", &n, &m) != EOF) { memset(f, 0x3f, sizeof(f)); memset(p, -1, sizeof(p)); while (m--) { int u, v, w; scanf("%d%d%d", &u, &v, &w); f[0][u][v] = w; p[u][v] = u; } floyd(); show(); } return 0; }
二维:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define MIN(x,y) (x<y?x:y) #define inf 0x3f3f3f3f #define N 111 int f[N][N]; // 状态数组,若存在 <vi, vj>, 则为其权值,否则为无穷大。 int p[N][N]; // p[i][j]: Vi 到 Vj 的最短路径上,Vj 的前一个结点 int n, m; void find(int i, int j) { if (p[i][j] == -1) { printf("该两点并不连通"); return; } if (p[i][j] != i) find(i, p[i][j]); else printf("%d", i); printf("->%d", j); } void show() // 输出所有最短路径 { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) continue; printf("点 %d 到 点 %d 的最短路径值:%d,最短路径:", i, j, f[i][j]); find(i, j); puts(""); } } } void floyd() { // 3,循环:试探所有顶点 for (int k = 1; k <= n; k++) // 遍历中间结点 { for (int i = 1; i <= n; i++) // 遍历起点 { for (int j = 1; j <= n; j++) // 遍历终点 { // 2,试探:尝试在所有路径中加入中间顶点 Vk,若距离变短,则修改状态数组和路径数组,否则不变。 if (f[i][k] + f[k][j] < f[i][j]) { f[i][j] = f[i][k] + f[k][j]; p[i][j] = p[k][j]; } } } } } int main(void) { while (scanf("%d%d", &n, &m) != EOF) { // 1,初始化:若存在 <vi, vj>, 则为其权值,否则为无穷大。 memset(f, 0x3f, sizeof(f)); memset(p, -1, sizeof(p)); while (m--) { int u, v, w; scanf("%d%d%d", &u, &v, &w); f[u][v] = w; p[u][v] = u; } floyd(); show(); } return 0; } /* 测试数据: 3 5 1 2 4 2 1 6 1 3 11 3 1 3 2 3 2 结果: 点 1 到 点 2 的最短路径值:4,最短路径:1->2 点 1 到 点 3 的最短路径值:6,最短路径:1->2->3 点 2 到 点 1 的最短路径值:5,最短路径:2->3->1 点 2 到 点 3 的最短路径值:2,最短路径:2->3 点 3 到 点 1 的最短路径值:3,最短路径:3->1 点 3 到 点 2 的最短路径值:7,最短路径:3->1->2 */
========== ========= ======== ======= ====== ===== ==== === == =
If there's any kind of magic in the world, it must be the attempt of understanding someone or share something.
— Before Sunrise