目录
[3、确定dp数组(dp table)以及下标的含义:](#3、确定dp数组(dp table)以及下标的含义:)
一、基础介绍
首相简单的说一下,Floyed算法 又称Floyd-Warshall算法,是为了纪念罗伯特•弗洛伊德(Robert W.Floyd)。所以不要对这个奇怪的名字感到吃力。
Floyd算法 是一种在具有正或负边缘权重(但没有负周期)的加权图 中找到最短路径 的算法。并且,它不同与Dijkstra、Bellman-Ford这种单源最短路径问题(即只能有一个起点)。而Floyed算法是多源最短路径问题,即 求多个起点到多个终点的多条最短路径。
二、核心思想
Floyd算法其核心思想是运用动态规划,通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。其时间与空间复杂度如下:
时间复杂度:O(n^3)
空间复杂度:O(n^2)
简而言之,Floyed算法 就是一种容易理解 ,可以算出任意两个节点之间的最短距离,代码编写简单的算法 。缺点就是时间复杂度比较高,不适合计算大量数据。
当然只是知道概念其实没那么重要,我从代码随线录中截取一个例题,相信通过这道题,大家定能受益匪浅。
三、核心例题
【题目描述】
小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。
给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),以及 M 条双向道路连接着这些景点。每条道路上行走的距离都是已知的。
小明有 Q 个观景计划,每个计划都有一个起点 start 和一个终点 end,表示他想从景点 start 前往景点 end。由于小明希望节省体力,他想知道每个观景计划中从起点到终点的最短路径长度。 请你帮助小明计算出每个观景计划的最短路径长度。
【输入描述】
第一行包含两个整数 N, M, 分别表示景点的数量和道路的数量。
接下来的 M 行,每行包含三个整数 u, v, w,表示景点 u 和景点 v 之间有一条长度为 w 的双向道路。
接下里的一行包含一个整数 Q,表示观景计划的数量。
接下来的 Q 行,每行包含两个整数 start, end,表示一个观景计划的起点和终点。
【输出描述】
对于每个观景计划,输出一行表示从起点到终点的最短路径长度。如果两个景点之间不存在路径,则输出 -1。
【输入示例】
7 3 1 2 4 2 5 6 3 6 8 2 1 2 2 3
【输出示例】
4 -1
【提示信息】
从 1 到 2 的路径长度为 4,2 到 3 之间并没有道路。
1 <= N, M, Q <= 1000.
我很喜欢的一个博主Carl,在讲解动态规划时,将动态规划分为五步,即为动规五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
1、引出为何用动态规划:
例如我们再求节点1 到 节点9 的最短距离,用二维数组来表示即:grid[1][9],如果最短距离是10 ,那就是 grid[1][9] = 10。
那 节点1 到 节点9 的最短距离 是不是可以由 节点1 到节点5的最短距离 + 节点5到节点9的最短距离组成呢?
即 grid[1][9] = grid[1][5] + grid[5][9]
节点1 到节点5的最短距离 是不是可以有 节点1 到 节点3的最短距离 + 节点3 到 节点5 的最短距离组成呢?
即 grid[1][5] = grid[1][3] + grid[3][5]
以此类推,节点1 到 节点3的最短距离 可以由更小的区间组成。到这个时候,已经递推公式已经开始明确喽。
在下方,我先给出算法,依照算法来讲解。
2、算法:
cpp
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main() {
int n, m, p1, p2, val;
cin >> n >> m;
vector<vector<vector<int>>> grid(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 10005))); // 因为边的最大距离是10^4
for(int i = 0; i < m; i++){
cin >> p1 >> p2 >> val;
grid[p1][p2][0] = val;
grid[p2][p1][0] = val; // 注意这里是双向图
}
// 开始 floyd
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]);
}
}
}
// 输出结果
int z, start, end;
cin >> z;
while (z--) {
cin >> start >> end;
if (grid[start][end][n] == 10005) cout << -1 << endl;
else cout << grid[start][end][n] << endl;
}
}
其核心就是
cpp
// 开始 floyd
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]);
}
}
}
// 别急,听我下发,细细讲解
3、确定dp数组(dp table)以及下标的含义:
在Floyd-Warshall 算法中,dp 数组(或grid数组) 是用来存储从一个节点到另一个节点的最短路径的。为了理解代码中的grid[i][j][k],我们先分析下标的含义。
1、i:表示起始节点,从1到n。
2、j:表示目标节点,从1到n。
3、k:表示我们是否考虑节点 k 作为中间节点(即,当前路径是否通过节点 k)。k 从 1 到 n。
grid[i][j][k] 表示从节点 i 到节点 j,在考虑了前 k 个节点作为中间节点的情况下的最短路径。
因此,grid[i][j][k] 的值是从节点 i 到节点 j,经过前 k 个节点的最短路径的长度。
4、确定递推公式:
递推公式是 Floyd-Warshall 算法的核心。我们使用动态规划的思想逐步更新最短路径。
-
状态转移公式:
grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1])
- grid[i][j][k - 1]:表示从节点 i 到节点 j,不经过节点
k
的最短路径。 - grid[i][k][k - 1] + grid[k][j][k - 1]:表示从节点 i 到节点 j ,经过节点
k
的最短路径。
这个递推公式的含义是:考虑是否通过节点 k 作为中间节点,如果通过节点 k 使得路径更短,则更新最短路径。
- grid[i][j][k - 1]:表示从节点 i 到节点 j,不经过节点
5、dp数组如何初始化:
在实际应用中,grid[i][j][0] 表示从节点 i 到节点 j 的最短路径(不经过任何节点)。
- 初始化 :
- 如果节点
i
和节点j
之间有直接边(即图中存在一条从i
到j
的边),则 grid[i][j][0] 初始化为这条边的权重。 - 如果没有直接边,则初始化为无穷大(10005)。
- grid[i][j][0]初始化为 0(因为一个节点到自己的路径是0)。
- 如果节点
总而言之,Floyed算法 就是一种容易理解 ,可以算出任意两个节点之间的最短距离,代码编写简单的算法 。缺点就是时间复杂度比较高,不适合计算大量数据。
好了,到这里基本结束。看完题目,相信大家有所收获。再见。( ̄︶ ̄)↗