Floyd 算法研究
理论基础
求最短路径Floyd算法!
Floyed(floyd)算法详解
Floyd-傻子也能看懂的弗洛伊德算法
最短路径Floyd算法【图文详解】
最短路径问题—Floyd算法详解
算法:最短路径之弗洛伊德(Floyd)算法
弗洛伊德(Floyd)算法求图的最短路径
《基于优化Floyd算法的室内机器人路径规划研究》
建议先看第一个 B 站视频和第三篇博客,能够对 Floyd 算法有快速的了解和认识
Floyd算法又称为插点法,是一种用于寻找给定的加权图中多源点
之间最短路径的算法。Floyd算法适用于解决任意两点间的最短路径的一种算法,同时也被用于计算有向图的传递闭包。此算法简单有效,而且由于其三重循环结构紧凑,对于稠密图,规划效率要高于Dijkstra算法
Floyd算法主要用来求多源、无负权边的最短路径,Floyd算法是一个经典的动态规划算法
从任意节点i
到任意节点j
的最短路径不外乎2种可能,一是直接从i
到j
,二是从i
经过若干个节点k
到j
我们假设
Dis(i,j)
为节点u
到节点v
的最短路径的距离,对于每一个节点k
,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)
是否成立,如果成立,证明从i
到k
再到j
的路径比i
直接到j
的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j)
,这样一来,当我们遍历完所有节点k
,Dis(i,j)
中记录的便是i到j的最短路径的距离
算法描述:
- 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大
- 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是则更新它
Floyd算法使用两个矩阵来进行最短路径的计算
- 首先,定义一个n×n的矩阵
D
,其中n是图中顶点的数量。矩阵D的元素D[i][j]表示顶点i
到顶点j
的最短路径的权值。初始时,矩阵D的元素被初始化为图中边的权值 - 然后,定义一个n×n的矩阵
P
,用于记录最短路径的前驱顶点。矩阵P的元素P[i][j]表示顶点i
到顶点j
的最短路径中,顶点j
的前驱顶点的编号。初始时,矩阵P的元素被初始化为顶点i
到顶点j
之间存在边时的前驱顶点编号 - 接下来,通过两层循环迭代计算矩阵D和P。在每一次迭代中,将顶点
k
作为中间节点,更新矩阵D
和P
的元素,以找到更短的路径。具体的更新规则是通过比较矩阵D[i][k] + D[k][j]与当前矩阵D[i][j]的大小来确定是否更新路径和更新前驱顶点 - 最后,完成所有迭代后,矩阵D中的元素即为每对顶点之间的最短路径的权值,矩阵P中的元素可以用于还原最短路径
上图中的 P 矩阵有误,D 矩阵很好理解,关键是 P 矩阵
, 主要涉及到 3 个问题
- P 矩阵如何初始化
- P 矩阵在循环中如何更新
- 如何通过 P 矩阵还原两点间最短路径
P 矩阵初始化
在使用Floyd算法之前,可以通过初始化矩阵P来为每对顶点之间的最短路径记录前驱节点
- 如果存在从节点 i 到节点 j 的边,则可以初始化P[i][j]为
i
,表示节点 i 是节点 j 的前驱节点,直接从节点 i 到节点 j 的路径是最短路径的一部分 - 如果不存在从节点 i 到节点 j 的边,则可以初始化P[i][j]为
-1
,表示节点i到节点 j 之间没有路径
下面是一个示例代码,展示如何初始化矩阵P:
#include <iostream>
#include <vector>
void initializeP(std::vector<std::vector<int>>& P, const std::vector<std::vector<int>>& graph, int numVertices) {
for (int i = 0; i < numVertices; ++i) {
for (int j = 0; j < numVertices; ++j) {
if (graph[i][j] != INF && i != j) {
P[i][j] = i;
} else {
P[i][j] = -1;
}
}
}
}
int main() {
int numVertices = 4;
std::vector<std::vector<int>> graph = {{0, 3, 8, INF},
{INF, 0, INF, 1},
{INF, 4, 0, INF},
{2, INF, -5, 0}};
std::vector<std::vector<int>> P(numVertices, std::vector<int>(numVertices));
// Initialize P matrix
initializeP(P, graph, numVertices);
// Print the initialized P matrix
for (int i = 0; i < numVertices; ++i) {
for (int j = 0; j < numVertices; ++j) {
std::cout << P[i][j] << " ";
}
std::cout << std::endl;
}
return 0;
}
P 矩阵在循环中更新
在Floyd算法中,矩阵P用于记录最短路径上的前驱顶点。下面是Floyd算法中P矩阵的更新规则:
- 初始化矩阵P:假设有一个图G,其中顶点的编号从1到 n。初始时,矩阵P的元素P[i][j]等于
i
,表示从顶点 i 到顶点 j 的最短路径上,顶点 j 的前驱顶点是顶点 i - 迭代更新P矩阵:Floyd算法通过逐步迭代更新矩阵P和最短路径矩阵D。在每一轮迭代中,检查顶点k是否可以作为顶点i到顶点j最短路径的中间顶点。如果可以,即存在更短的路径,更新矩阵P中对应的元素P[i][j]为
k
具体的更新规则如下:
for k = 1 to n do
for i = 1 to n do
for j = 1 to n do
if (D[i][j] > D[i][k] + D[k][j]) then
D[i][j] = D[i][k] + D[k][j]
P[i][j] = P[k][j]
其中,D矩阵表示顶点之间的最短路径长度。如果在第 k 轮迭代中发现从顶点 i 到顶点 j 的路径经过顶点 k 时更短,那么更新D[i][j]为更小的路径长度,并且更新P[i][j]为顶点 k
,表示顶点 j 的前驱顶点是顶点 k
通过这样的迭代更新,最终可以得到最短路径矩阵D
和前驱矩阵P
由 P 矩阵还原最短路径
Floyd算法中使用矩阵P来记录最短路径的前驱节点。通过P矩阵可以还原最短路径。下面是还原最短路径的步骤:
- 首先,如果
P[i][j] == -1
,则表示从节点i到节点j不存在路径,即没有最短路径可还原 - 如果
P[i][j] == i
,表示节点j
的前驱节点就是i
,即直接从节点 i 到节点 j 的路径为最短路径的一部分 - 如果
P[i][j] ≠ i
,则表示节点j
的前驱节点是P[i][j]
。需要递归地从节点j
到节点P[i][j]
还原路径,并输出节点 j,即先还原前面的节点,再输出节点 j
D矩阵非常容易理解。难点在于P矩阵,不好定义,从而产生误解。其实P矩阵可以理解为记录顶点间最短路径的目标点的前置跳转点,它并不是直接可以得出最短路径。也就是说P矩阵里的一个值并不能得出最短路径,而是需要不断的迭代,将上一步的前置跳转点作为这一步的目标点,继续查找当前目标点的前置跳转点,这样进行下来,直到查到起点结束。这样才能得出真正的最短路径
Talk is always cheap. 以第三篇博客中的内容作为示例,编写测试代码
#include <vector>
#include <iostream>
using namespace std;
template<typename T>
void printArr(vector<T> arr)
{
for (T val : arr)
{
cout << val << "\t";
}
cout << endl;
}
template<typename T>
void printTwoDimensationArr(vector<vector<T>> arr)
{
int n = arr.size();
for (int i = 0; i < n; ++i)
{
printArr<T>(arr[i]);
}
cout << endl;
}
#define INF 99999
void restorePath(int i, int j, const vector<vector<int>>& P) {
if (i == j) {
cout << i << " ";
}
else if (P[i][j] == -1) {
cout << "No path exists";
}
else {
restorePath(i, P[i][j], P);
cout << j << " ";
}
}
void floydAlgorithm(vector<vector<int>>& graph, int numVertices) {
vector<vector<int>> dist(numVertices, vector<int>(numVertices));
vector<vector<int>> P(numVertices, vector<int>(numVertices));
// Initialize dist and P matrices
for (int i = 0; i < numVertices; ++i) {
for (int j = 0; j < numVertices; ++j) {
dist[i][j] = graph[i][j];
if (i == j || graph[i][j] == INF) {
P[i][j] = -1;
}
else {
P[i][j] = i;
}
}
}
// Floyd algorithm
cout << "Distance Array:" << endl;
printTwoDimensationArr<int>(dist);
cout << "Path Array:" << endl;
printTwoDimensationArr<int>(P);
for (int k = 0; k < numVertices; ++k) {
for (int i = 0; i < numVertices; ++i) {
for (int j = 0; j < numVertices; ++j) {
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
P[i][j] = P[k][j];
}
}
}
cout << "Distance Array:" << endl;
printTwoDimensationArr<int>(dist);
cout << "Path Array:" << endl;
printTwoDimensationArr<int>(P);
}
// Print shortest paths
for (int i = 0; i < numVertices; ++i) {
for (int j = 0; j < numVertices; ++j) {
if (i != j) {
cout << "Shortest path from " << i << " to " << j << ": ";
restorePath(i, j, P);
cout << endl;
}
}
}
}
int main() {
int numVertices = 4;
vector<vector<int>> graph = { {0, 2, 6, 4},
{INF, 0, 3, INF},
{7, INF, 0, 1},
{5, INF, 12, 0} };
floydAlgorithm(graph, numVertices);
return 0;
}
在上述示例代码中,graph
表示图的邻接矩阵,其中INF
表示两个顶点之间不存在直接边的情况。numVertices
表示顶点的数量。程序通过Floyd算法计算最短路径,并使用restorePath
函数通过 P 矩阵还原最短路径
输出如下
Distance Array:
0 2 6 4
99999 0 3 99999
7 99999 0 1
5 99999 12 0
Path Array:
-1 0 0 0
-1 -1 1 -1
2 -1 -1 2
3 -1 3 -1
Distance Array:
0 2 6 4
99999 0 3 99999
7 9 0 1
5 7 11 0
Path Array:
-1 0 0 0
-1 -1 1 -1
2 0 -1 2
3 0 0 -1
Distance Array:
0 2 5 4
99999 0 3 99999
7 9 0 1
5 7 10 0
Path Array:
-1 0 1 0
-1 -1 1 -1
2 0 -1 2
3 0 1 -1
Distance Array:
0 2 5 4
10 0 3 4
7 9 0 1
5 7 10 0
Path Array:
-1 0 1 0
2 -1 1 2
2 0 -1 2
3 0 1 -1
Distance Array:
0 2 5 4
9 0 3 4
6 8 0 1
5 7 10 0
Path Array:
-1 0 1 0
3 -1 1 2
3 0 -1 2
3 0 1 -1
Shortest path from 0 to 1: 0 1
Shortest path from 0 to 2: 0 1 2
Shortest path from 0 to 3: 0 3
Shortest path from 1 to 0: 1 2 3 0
Shortest path from 1 to 2: 1 2
Shortest path from 1 to 3: 1 2 3
Shortest path from 2 to 0: 2 3 0
Shortest path from 2 to 1: 2 3 0 1
Shortest path from 2 to 3: 2 3
Shortest path from 3 to 0: 3 0
Shortest path from 3 to 1: 3 0 1
Shortest path from 3 to 2: 3 0 1 2
手推了一遍 D 矩阵和 P 矩阵的更新过程,P 矩阵的初始化有些小瑕疵,有些位置没有初始化为 -1
右侧简单模拟了由 1 号节点到 0 号节点通过递归还原路径的过程