Floyd-Warshall算法利用动态规划的思想,通过迭代更新距离矩阵来逐步逼近所有节点对之间的最短路径,其核心思想是考虑中间节点,利用子问题的最优解来求解整个问题的最优解。
4.2.1 基本思想
Floyd-Warshall算法的基本思想是动态规划,可以将其基本思想归纳为如下所示的几点。
- 利用中间节点:Floyd-Warshall算法通过考虑中间节点来求解所有节点对之间的最短路径。对于每一对节点(i, j),算法尝试通过其他节点k来确定是否存在一条路径比当前已知的路径更短。
- 迭代更新距离矩阵:算法通过迭代地更新距离矩阵来逐步逼近所有节点对之间的最短路径。在每一轮迭代中,对于每一对节点(i, j),算法尝试通过节点k来更新节点i到节点j的距离,如果存在一条路径经过节点k比当前已知的路径更短,则更新距离矩阵中对应的距离值。
- 动态规划转移方程:算法通过动态规划的思想,利用子问题的最优解来求解整个问题的最优解。具体来说,更新节点i到节点j的距离时,算法考虑从节点i到节点j的直接路径以及通过中间节点k的路径,并选择其中距离最短的路径作为节点i到节点j的最短路径。
- 最终结果:经过多轮迭代后,距离矩阵中的值将会收敛到所有节点对之间的最短路径长度。最终,距离矩阵中的值将反映出所有节点之间的最短路径长度。
4.2.2 图的表示方法
在Floyd-Warshall算法中,通常使用邻接矩阵来表示图。邻接矩阵是一个二维数组,其中的元素表示图中节点之间的连接关系和权重。对于有向图,矩阵的(i, j)元素表示从节点i到节点j的边的权重;对于无向图,则可以在(i, j)和(j, i)位置都标记上边的权重。
举例来说,如果有一个带权有向图,其邻接矩阵可能如下所示:
| 1 2 3 4
----------------------------------------------------
1 | 0 3 ∞ 7
2 | ∞ 0 2 ∞
3 | 8 ∞ 0 1
4 | ∞ ∞ ∞ 0
在这个矩阵中,如果存在一条从节点i到节点j的边,则对应的矩阵元素(i, j)的值为该边的权重;如果不存在这样的边,则矩阵元素的值为∞。
4.2.3 Floyd-Warshall算法的实现步骤
Floyd-Warshall算法是一种动态规划算法,用于解决所有节点对之间的最短路径问题。其原理和实现步骤如下所示。
(1)初始化距离矩阵:创建一个二维数组dist来存储任意两个节点之间的最短路径长度。如果节点i到节点j之间没有直接连接的边,则dist[i][j]的值为无穷大(INF);如果存在直接连接的边,则dist[i][j]的值为该边的权重。
(2)动态规划更新距离矩阵:对于每一个节点k,遍历所有节点对(i, j),检查是否存在一条从节点i经过节点k到节点j的路径比已知的路径更短。如果存在这样的路径,则更新dist[i][j]为更短的路径长度dist[i][k] + dist[k][j]。
(3)重复执行步骤(2):重复执行步骤(2)直到所有节点对之间的最短路径长度都已确定。即对每个节点k,都进行一次更新操作。
(4)检查负权环路:最后,检查距离矩阵中对角线上的元素。如果任何一个对角线上的元素为负值,则表示存在负权环路,因为从一个节点到自身的最短路径长度应该为0,而负值表示存在更短的路径。
(5)输出结果:最终,dist矩阵中的值即为所有节点对之间的最短路径长度。
例如下面是Floyd-Warshall算法的伪代码,其中,graph表示输入的图,weight(u,v)表示从节点u到节点v的权重,dist表示距离矩阵,|V|表示图中节点的数量。
procedure FloydWarshall(graph)
// 初始化距离矩阵
for each vertex v
dist[v][v] ← 0
for each edge (u,v) in graph
dist[u][v] ← weight(u,v)
// 动态规划更新距离矩阵
for k from 1 to |V|
for i from 1 to |V|
for j from 1 to |V|
if dist[i][k] + dist[k][j] < dist[i][j]
dist[i][j] ← dist[i][k] + dist[k][j]
// 检查负权环路
for each vertex v
if dist[v][v] < 0
return "Graph contains a negative-weight cycle"
return dist
4.2.4 Floyd-Warshall算法的推导过程
假设我们有一个带权有向图,其中包含了4个节点(A、B、C、D)和对应的边的权重。现在将这个有向图转换为邻接矩阵表示,用∞表示不可达的情况:
A B C D
-------------------------
A | 0 2 ∞ 1
B | ∞ 0 3 ∞
C | ∞ ∞ 0 1
D | ∞ ∞ ∞ 0
接下来,我们将使用Floyd-Warshall算法的迭代步骤,更新距离矩阵中的值。具体迭代的过程如下所示。
(1)第1轮迭代结果如下所示:
A B C D
------------------------------
A | 0 2 ∞ 1
B | ∞ 0 3 ∞
C | ∞ ∞ 0 1
D | ∞ ∞ ∞ 0
以节点A作为中间节点:
dist(B, C) = min(dist(B, C), dist(B, A) + dist(A, C)) = min(∞, ∞ + ∞) = ∞
dist(B, D) = min(dist(B, D), dist(B, A) + dist(A, D)) = min(∞, ∞ + 1) = ∞
dist(C, B) = min(dist(C, B), dist(C, A) + dist(A, B)) = min(∞, ∞ + 2) = ∞
dist(C, D) = min(dist(C, D), dist(C, A) + dist(A, D)) = min(∞, ∞ + 1) = ∞
dist(D, B) = min(dist(D, B), dist(D, A) + dist(A, B)) = min(∞, ∞ + 2) = ∞
dist(D, C) = min(dist(D, C), dist(D, A) + dist(A, C)) = min(∞, ∞ + ∞) = ∞
(2)第2轮迭代结果如下所示:
A B C D
-----------------------------------
A | 0 2 ∞ 1
B | ∞ 0 3 ∞
C | ∞ ∞ 0 1
D | ∞ ∞ ∞ 0
以节点B作为中间节点:
dist(C, A) = min(dist(C, A), dist(C, B) + dist(B, A)) = min(∞, 3 + 2) = 5
dist(C, D) = min(dist(C, D), dist(C, B) + dist(B, D)) = min(∞, 3 + ∞) = ∞
dist(D, A) = min(dist(D, A), dist(D, B) + dist(B, A)) = min(∞, ∞ + 2) = ∞
dist(D, C) = min(dist(D, C), dist(D, B) + dist(B, C)) = min(∞, ∞ + 1) = ∞
(3)第3轮迭代结果如下所示:
A B C D
--------------------------------
A | 0 2 ∞ 1
B | ∞ 0 3 ∞
C | ∞ 5 0 1
D | ∞ ∞ ∞ 0
以节点C作为中间节点:
dist(D, A) = min(dist(D, A), dist(D, C) + dist(C, A)) = min(∞, 1 + 5) = 6
dist(D, B) = min(dist(D, B), dist(D, C) + dist(C, B)) = min(∞, 1 + 3) = 4
(4)第4轮迭代结果如下所示:
A B C D
------------------------------------
A | 0 2 6 1
B | ∞ 0 3 4
C | ∞ 5 0 1
D | ∞ ∞ ∞ 0
最终得到的距离矩阵即为所有节点之间的最短路径长度。例如,从节点A到节点D的最短路径长度为1,从节点B到节点C的最短路径长度为3。
例如在下面的例子中,假设图中的顶点数为4,顶点索引从0到3,分别表示为A、B、C、D。下面的代码实现了 Floyd-Warshall 算法,用于求解给定图中所有顶点对之间的最短路径距离。该实例通过构建邻接矩阵表示图,然后使用Floyd-Warshall 算法进行计算,并输出每对顶点之间的最短距离矩阵。
实例4-1:使用Floyd-Warshall算法计算最短距离(codes/4/Floyd/Floyd.cpp)
实例文件Floyd.cpp的具体实现代码如下所示。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
#define INF 99999
void floydWarshall(Mat& graph) {
int V = graph.rows;
// 初始化距离矩阵
Mat dist = graph.clone();
// 更新距离矩阵
for (int k = 0; k < V; k++) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (dist.at<int>(i, k) + dist.at<int>(k, j) < dist.at<int>(i, j)) {
dist.at<int>(i, j) = dist.at<int>(i, k) + dist.at<int>(k, j);
}
}
}
}
// 输出最短距离矩阵
cout << "Shortest distances between every pair of vertices:" << endl;
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (dist.at<int>(i, j) == INF) {
cout << "INF ";
} else {
cout << dist.at<int>(i, j) << " ";
}
}
cout << endl;
}
}
int main() {
// 构建邻接矩阵
Mat graph = (Mat_<int>(4, 4) << 0, 5, INF, 10,
INF, 0, 3, INF,
INF, INF, 0, 1,
INF, INF, INF, 0);
// 运行Floyd-Warshall算法
floydWarshall(graph);
return 0;
}
上述代码的实现流程如下所示:
- 首先,定义了一个常量INF表示无穷大的距离值,以及一个函数floydWarshall用于执行Floyd-Warshall算法。
- 然后,在主函数main中,通过构建一个邻接矩阵表示图的结构。这个图是一个4x4的矩阵,表示了4个顶点之间的连接情况及对应的权重。
- 接着,调用floydWarshall函数,传入邻接矩阵作为参数。在floydWarshall函数中,使用Floyd-Warshall算法计算图中所有顶点对之间的最短路径距离。
- 最后,输出显示最短路径距离矩阵,展示每对顶点之间的最短距离。执行后会输出:
Shortest distances between every pair of vertices:
0 5 8 9
INF 0 3 4
INF INF 0 1
INF INF INF 0
在上面的输出结果中,第一行表示从顶点0到其他顶点的最短距离,第二行表示从顶点1到其他顶点的最短距离,以此类推。INF代表无穷大的距离,表示两个顶点之间没有直接连接的路径。
4.2.5 Floyd-Warshall算法与其他路径规划算法的对比
Floyd-Warshall算法和其他路径规划算法相比具有一些优势和劣势,下面是它与其他常见路径规划算法的对比。
1. Dijkstra算法
- 适用范围:Dijkstra算法适用于解决单源最短路径问题,即计算从单个源节点到其他所有节点的最短路径。
- 时间复杂度:Dijkstra算法的时间复杂度取决于所采用的数据结构,在最坏情况下可达到O(V^2),或者通过使用优先队列优化后可达到O(E + V log V),其中V是节点数量,E是边数量。
- 负权边:Dijkstra算法不能处理带有负权边的图。
2. Bellman-Ford算法
- 适用范围:Bellman-Ford算法适用于解决单源最短路径问题,且可以处理带有负权边但不含负权环的图。
- 时间复杂度:Bellman-Ford算法的时间复杂度为O(V*E),其中V是节点数量,E是边数量。
- 负权环路:如果图中存在负权环路,Bellman-Ford算法能够检测并报告。
3. Floyd-Warshall算法
- 适用范围:Floyd-Warshall算法适用于解决所有节点对之间的最短路径问题,能够计算任意两个节点之间的最短路径。
- 时间复杂度:Floyd-Warshall算法的时间复杂度为O(V^3),其中V是节点数量。
- 负权环路:Floyd-Warshall算法不能处理带有负权环路的图,但它能够检测到负权环路的存在。
4. A*算法
- 适用范围:A*算法适用于解决单源最短路径问题,且可以利用启发式函数加速搜索。
- 时间复杂度:A*算法的时间复杂度取决于所采用的启发式函数,通常情况下比Dijkstra算法更快,但可能不如Dijkstra算法精确。
- 负权边:A*算法不能处理带有负权边的图。
综上所述,选择合适的路径规划算法取决于问题的具体情况,包括图的规模、边的属性(如是否存在负权边)、搜索的目标等。Floyd-Warshall算法适用于需要计算所有节点对之间的最短路径的情况,但由于其时间复杂度较高,不适合用于大规模图的计算。