小明逛公园-(Floyd 算法)

题目链接: 小明逛公园
学习了代码随想录 Floyd 算法精讲 这篇内容,该类问题是多源多宿最短路问题,如果使用单源最短路算法例如Dijkstra,BF,则需要重复进行 n 次(n 为 源点的个数)。
Floyd 算法用来解决多源多宿最短路问题,Floyd 算法对边的权值正负没有要求,都可以处理,其核心思想是动态规划。

  1. 确定dp数组(dp table)以及下标的含义
    grid[i][j][k] = m,表示 节点i 到 节点j 以[1…k] 集合为中间节点的最短距离为m。

  2. 确定递推公式
    – 节点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])

  3. 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],其余未连结的点之间初始化为一个最大数,防止影响计算结果

  4. 遍历顺序
    从递推公式: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;
}
  • 23
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值