Floyd 算法--多源最短路
一、题目描述
二、解题思路-Floyd算法
(1)最优子结构性质
(2)动规五部曲
三、最终代码
滚动数组优化
一、题目描述
二、解题思路-Floyd算法
在这之前我们讲解过,dijkstra、Bellman算法都是单源最短路,即只能有一个起点。
而本题是多源最短路,即 求多个起点到多个终点的多条最短路径。通过本题,我们来系统讲解一个新的最短路算法-Floyd 算法。【当需要计算所有顶点对之间的最短路径时,如果 源点少,其实可以 多次dijsktra 求源点到终点。s】
迪杰斯特拉算法每次只能计算一个起点到所有其他顶点的最短路径。
若要计算所有顶点对之间的最短路径,需要对每个顶点都运行一次迪杰斯特拉算法,时间复杂度太大
先说几个Floyd算法的要点:Floyd 算法对边的权值正负没有要求,都可以处理,但是不允许有负环。 Floyd算法核心思想是动态规划。
(1)最优子结构性质
(2)动规五部曲
之前在讲解动态规划的时候,给出过动规五部曲:
- 确定dp数组(dp table)以及下标的含义
这里我们用 grid数组来存图,那就把dp数组命名为 grid。
grid[i][j][k] = m,表示 节点i 到 节点j 以[1...k] 集合中的一个节点为中间节点的最短距离为m。
why???
节点i 到 节点j 的最短路径中 一定是经过很多节点,那么这个集合用[1...k] 来表示。
你可以反过来想,节点i 到 节点j 中间一定经过很多节点,那么你能用什么方式来表述中间这么多节点呢?
所以 这里的k不能单独指某个节点,k 一定要表示一个集合,即[1...k] ,表示节点1 到 节点k 一共k个节点的集合。
- 确定递推公式
在上面的分析中我们已经初步感受到了递推的关系。
我们分两种情况:
1. 节点i 到 节点j 的最短路径经过节点k : grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1]
节点i 到 节点k 的最短距离 不经过节点k,中间节点集合为[1...k-1],所以 表示为
grid[i][k][k - 1]
,同理节点k 到 节点j 的最短距离 也是不经过节点k,表示为grid[k][j][k - 1]
2.节点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 =0;本题求的是最小值,所以输入数据没有涉及到的节点的情况都应该初始为一个最大数。
vector<vector<vector<int>>> grid(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 10005))); // C++定义了一个三位数组,10005是因为边的最大距离是10^4for(int i = 0; i < m; i++){cin >> p1 >> p2 >> val;grid[p1][p2][0] = val;grid[p2][p1][0] = val; // 注意这里是双向图 }
- 确定遍历顺序
从递推公式:
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 依赖于 k - 1, i 和j 到 并不依赖与 i - 1 或者 j - 1 等等。
那么这三个for的嵌套顺序应该是什么样的呢?
我们是把 k =0 的 i 和j 对应的数值都初始化了,这样才能去计算 k = 1 的时候 i 和 j 对应的数值。这就好比是一个三维坐标,i 和j 是平层,而k 是 垂直向上 的。遍历的顺序是从底向上 一层一层去遍历。所以遍历k 的for循环一定是在最外面,这样才能一层一层去遍历。如图
至于遍历 i 和 j 的话,for 循环的先后顺序无所谓。
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][j][k-1], grid[i][k][k-1] + grid[k][j][k-1]);}} }
- 举例推导dp数组
三、最终代码
//所有顶点对之间的最短路径---Floyd 算法
#include<bits/stdc++.h>
using namespace std;
int main()
{int n,m;cin>>n>>m;vector<vector<vector<int>>> grid(n+1,vector<vector<int>>(n+1,vector<int>(n+1,10005)));for(int i=1; i<=n; i++) grid[i][i][0] = 0; //自己到自己是0//存储图for(int i=0;i<m;i++){int u,v,w;cin>>u>>v>>w;if(w<grid[u][v][0]) //处理重边(有时候题目会说没有重边)grid[u][v][0]=w; //双向图grid[v][u][0]=w;}//开始floydfor(int k=1;k<=n;k++) //最外层遍历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]);}}}//输出结果int q;cin>>q;while(q--){int sta,end;cin>>sta>>end;if(grid[sta][end][n]==10005) cout<<"-1"<<endl;else cout<<grid[sta][end][n]<<endl;}return 0;
}
滚动数组优化
#include <iostream>
#include <vector>
using namespace std;int main() {int n, m, p1, p2, val;cin >> n >> m;vector<vector<int>> grid(n + 1, vector<int>(n + 1, 10005)); // 因为边的最大距离是10^4for(int i = 0; i < m; i++){cin >> p1 >> p2 >> val;grid[p1][p2] = val;grid[p2][p1] = val; // 注意这里是双向图}// 开始 floydfor (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][j], grid[i][k] + grid[k][j]);}}}// 输出结果int z, start, end;cin >> z;while (z--) {cin >> start >> end;if (grid[start][end] == 10005) cout << -1 << endl;else cout << grid[start][end] << endl;}
}