题目链接: 小明逛公园
学习了代码随想录 Floyd 算法精讲 这篇内容,该类问题是多源多宿最短路问题,如果使用单源最短路算法例如Dijkstra,BF,则需要重复进行 n 次(n 为 源点的个数)。
Floyd 算法用来解决多源多宿最短路问题,Floyd 算法对边的权值正负没有要求,都可以处理,其核心思想是动态规划。
-
确定dp数组(dp table)以及下标的含义
grid[i][j][k] = m,表示 节点i 到 节点j 以[1…k] 集合为中间节点的最短距离为m。 -
确定递推公式
– 节点i 到 节点j 的最短路径经过节点k,grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1]
– 节点i 到 节点j 的最短路径不经过节点k,grid[i][j][k] = grid[i][j][k - 1]
对其求的是最小值,因此递推公式是:
grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1]) -
dp数组如何初始化
grid[i][j][k] = m,表示 节点i 到 节点j 以[1…k] 集合为中间节点的最短距离为m。刚开始初始化k 是不确定的,只能把 k 赋值为 0,本题节点 0 是无意义的,节点是从 1 到 n。
这样我们在下一轮计算的时候,就可以根据 grid[i][j][0] 来计算 grid[i][j][1],此时的 grid[i][j][1] 就是 节点i 经过节点1 到达 节点j 的最小距离了。
因此初始化 grid[i][j][0] = value[i][j],其余未连结的点之间初始化为一个最大数,防止影响计算结果 -
遍历顺序
从递推公式:grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1]) 可以看出,我们需要三个for循环,分别遍历i,j 和k
在初始化时,我们是把 k =0 的 i 和j 对应的数值都初始化了,这样才能去计算 k = 1 的时候 i 和 j 对应的数值。这就好比是一个三维坐标,i 和j 是平层,而k 是 垂直向上 的。遍历的顺序是从底向上 一层一层去遍历。所以遍历k 的for循环一定是在最外面,这样才能一层一层去遍历。
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m,u,v,w;
int Q, start, end;
cin >> n >> m;
vector<vector<vector<int>>> grid(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 1000000)));
while(m--){
cin >> u >> v >> w;
grid[u][v][0] = w;
grid[v][u][0] = w;
}
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1]);
}
}
}
cin >> Q;
while(Q--){
cin >> start >> end;
if(grid[start][end][n] == 1000000){
cout << -1 << endl;
}else{
cout << grid[start][end][n] << endl;
}
}
return 0;
}
空间优化:
从滚动数组的角度来看,我们定义一个 grid[n + 1][ n + 1][2] 这么大的数组就可以,因为k 只是依赖于 k-1的状态,并不需要记录k-2,k-3,k-4 等等这些状态。
那么我们只需要记录 grid[i][j][1] 和 grid[i][j][0] 就好,之后就是 grid[i][j][1] 和 grid[i][j][0] 交替滚动。
在进一步想,如果本层计算(本层计算即k相同,从三维角度来讲) gird[i][j] 用到了 本层中刚计算好的 grid[i][k] 会有什么问题吗?
如果 本层刚计算好的 grid[i][k] 比上一层 (即k-1层)计算的 grid[i][k] 小,说明确实有 i 到 k 的更短路径,那么基于 更小的 grid[i][k] 去计算 gird[i][j] 没有问题。
如果 本层刚计算好的 grid[i][k] 比上一层 (即k-1层)计算的 grid[i][k] 大, 这不可能,因为这样也不会做更新 grid[i][k]的操作。
所以本层计算中,使用了本层计算过的 grid[i][k] 和 grid[k][j] 是没问题的。
那么就没必要区分,grid[i][k] 和 grid[k][j] 是 属于 k - 1 层的呢,还是 k 层的。
因此,递推公式可以修改为: grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j]);
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m,u,v,w;
int Q, start, end;
cin >> n >> m;
vector<vector<int>> grid(n + 1, vector<int>(n + 1, 1000000));
while(m--){
cin >> u >> v >> w;
grid[u][v] = w;
grid[v][u] = w;
}
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
grid[i][j] = min(grid[i][k] + grid[k][j], grid[i][j]);
}
}
}
cin >> Q;
while(Q--){
cin >> start >> end;
if(grid[start][end] == 1000000){
cout << -1 << endl;
}else{
cout << grid[start][end] << endl;
}
}
return 0;
}