Floyd 全源最短路径算法笔记

Allen2010 Lv2

算法描述

三个破变量,一共就十行。编程十分钟,运行一晚上。

很多人认为 Floyd 就是简单的动态规划,甚至有人直接把它当模板背了下来,导致不会变通而 WA 了 P1119。然而其实大多数初学者包括我一开始都理解错了它,包括原理。

算法实现

定义 表示只包含前 个节点时 的最短路。而加不加入这个 号元素前后有什么区别呢?无非就是某两个点的最短路通过 号点松弛出了更有的最短路嘛。于是就有了经典代码:

1
2
3
4
5
6
7
8
9
void Floyd(){
for(ll k = 1; k <= n; k ++){
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
}

那么如何初始化呢?如果 有路,就初始化为边长。否则初始化为最大值。
1
2
3
4
5
6
7
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
if(i == j)dis[i][j] = 0;
else if(gph[i][j])dis[i][j] = gph[i][j];
else dis[i][j] = INF;
}
}

这就完了吗?这值得写博客吗?当然不值。虽然我本来就是给自己写来复习的

Floyd应用

Floyd解决传递闭包问题

题目传送门

在有向图中,定义 如果是 1,则表示 能到达 ,否则反之。

这该如何解决呢?转换一下定义。
定义 表示只有前 个点时 能否到达 。同理,加入了一个元素 之后无非就是有一组 能通过它到达 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Floyd(){
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
if(graph[i][j])d[i][j] = 1;
else d[i][j] = 0;
}
}
for(ll k = 1; k <= n; k ++){
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
d[i][j] = d[i][j] || (d[i][k] && d[k][j]);
}
}
}
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
cout << d[i][j] << " ";
}
cout << "\n";
}
}

Floyd求最小环

依旧是改定义: 为前 个节点中可以确定的最小环的大小。则匹配最短路,可以在算最短路之后算环了,可以理解为以 为一个顶点,由 可以到达的 以及 也就是最短路构成的环。然后枚举已经算出最短路的 即可。
例题就是 P2738 篱笆回路。这题输入特别逆天,所以给了完整代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll MAXN = 305;
const ll INF = 2147483647;

struct Edge{
ll u, v, w;
bool lside[MAXN], rside[MAXN];
}edge[MAXN];

ll adj[MAXN][MAXN], father[MAXN], m, n, idx[MAXN], dis[MAXN][MAXN];

ll find(ll x){
if(father[x] == x)return x;
return father[x] = find(father[x]);
}

void merge(ll x, ll y){
father[find(x)] = find(y);
}

ll Floyd(){
ll ans = INF;
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
if(!dis[i][j])dis[i][j] = INF;
if(!adj[i][j])adj[i][j] = INF;
}
}
for(ll i = 1; i <= n; i ++){
adj[i][i] = dis[i][i] = 0;
}
for(ll k = 1; k <= n; k ++){
for(ll i = 1; i <= k - 1; i ++){
for(ll j = i + 1; j <= k - 1; j ++){
ans = min(ans, adj[i][k] + adj[k][j] + dis[i][j]);
}
}
for(ll i = 1; i <= n; i ++){
for(ll j = 1; j <= n; j ++){
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
return ans;
}

int main(){
cin >> m;
for(ll i = 1; i <= m; i ++){
ll s, len, numl, numr, id;
cin >> s >> len >> numl >> numr;
edge[s].u = s * 2 - 1;
edge[s].v = s * 2;
edge[s].w = len;
for(ll j = 1; j <= numl; j ++){
cin >> id;
edge[s].lside[id] = 1;
}
for(ll j = 1; j <= numr; j ++){
cin >> id;
edge[s].rside[id] = 1;
}
}
for(ll i = 1; i <= 250; i ++){
father[i] = i;
}
for(ll i = 1; i <= m; i ++){
for(ll j = 1; j <= m; j ++){
if(i == j)continue;
if(edge[i].lside[j] && edge[j].lside[i]){
merge(edge[i].u, edge[j].u);
}
if(edge[i].lside[j] && edge[j].rside[i]){
merge(edge[i].u, edge[j].v);
}
if(edge[i].rside[j] && edge[j].lside[i]){
merge(edge[i].v, edge[j].u);
}
if(edge[i].rside[j] && edge[j].rside[i]){
merge(edge[i].v, edge[j].v);
}
}
}
for(ll i = 1; i <= 2 * m; i ++){
if(father[i] == i){
idx[i] = ++n;
}
}
for(ll i = 1; i <= m; i ++){
edge[i].u = idx[find(edge[i].u)];
edge[i].v = idx[find(edge[i].v)];
adj[edge[i].u][edge[i].v] = edge[i].w;
adj[edge[i].v][edge[i].u] = edge[i].w;
dis[edge[i].u][edge[i].v] = edge[i].w;
dis[edge[i].v][edge[i].u] = edge[i].w;
}
cout << Floyd() << "\n";
return 0;
}

总结

Floyd 算法似乎从方方面面看都很平庸:时间复杂度高达 ,需要空间复杂度耗费较大的邻接矩阵存图,甚至连模板的使用环境都比较小。
然而 Floyd 算法具有较高的灵活性、可读性和可写性。一旦深刻理解了它的原理,就可以加以变通,解决许许多多看似复杂的算法。以上就是例子,希望大家不要想我最初一样,只背了模板,没有思考含义。

Floyd 算法之所以被发明出来,并不是因为他比 Dijkstra 更快,比 SPFA 更好理解,而是因为,他善啊……


推荐例题:洛谷 P1119 灾后重建,P1841 重要的城市

  • Title: Floyd 全源最短路径算法笔记
  • Author: Allen2010
  • Created at : 2024-10-13 00:00:00
  • Updated at : 2025-03-25 22:24:34
  • Link: https://allen2010.github.io/2024/10/13/Floyd Path/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments