图论相关帖子
- 基本概念
- 图的表示: 邻接矩阵和邻接表
- 图的遍历: 深度优先与广度优先
- 拓扑排序
- 图的最短路径:Dijkstra算法和Bellman-Ford算法
- 最小生成树: Prim算法和Kruskal算法
- 二分图
- 多源最短路径: Floyd-Warshall算法和Johnson算法
- 强连通分量: Kosaraju算法和Tarjan算法
- 欧拉回路: Fleury算法和Hierholzer算法
- 网络流算法: Edmonds-Karp算法
- 网络流算法: Dinic算法
环境要求
本文所用样例在Windows 11
以及Ubuntu 24.04
上面编译通过.
- Windows: 使用[Visual Studio],
- Ubuntu: 使用 Clang 18.1.3. (Ubuntu 24.04 系统安装版本)
- GCC 无法编译直接本项目代码, 因为本文代码使用了 C++20 Module, 而 GCC 对此支持不完整.
Intro
多源最短路径问题是图论中的一个经典问题, 它要求找到图中所有顶点对之间的最短路径. 这个问题可以通过几种不同的算法来解决, 其中最为著名的包括 Floyd-Warshall Algorithm 和 Johnson’s Algorithm.
Floyd-Warshall 算法
弗洛伊德-沃沙尔算法(Floyd-Warshall Algorithm) 是一种用于解决所有顶点对最短路径问题的经典动态规划算法. 它适用于加权图, 包括带有负权重边的图, 但不允许存在负权重环. 该算法的时间复杂度为 O ( V 3 ) O(V^3) O(V3), 其中 V V V 是图中顶点的数量, 因此对于中小规模的完全图或接近完全图特别有效.
算法原理
弗洛伊德-沃沙尔算法的核心思想是逐步尝试通过每个顶点作为中间节点, 更新所有可能的顶点对之间的最短路径估计值. 具体来说, 算法使用一个二维数组 D
来存储顶点间的最短路径长度, 其中 D[i][j]
表示从顶点 i
到顶点 j
的最短路径长度. 初始时, D[i][j]
等于图中顶点 i
和 j
之间的直接距离(如果存在), 或者设置为无穷大(如果没有直接连接).
算法的基本步骤如下:
-
初始化: 根据图的邻接矩阵初始化距离矩阵
D
. 如果两个顶点之间没有直接连接, 则将相应的D[i][j]
设为无穷大; 如果i=j
, 则D[i][i]=0
. -
迭代更新: 对于每一对顶点
(i, j)
, 考虑每一个顶点k
作为中间节点, 检查是否可以通过k
改进i
到j
的路径:- 如果
D[i][k] + D[k][j] < D[i][j]
, 则更新D[i][j] = D[i][k] + D[k][j]
. - 这个过程需要遍历所有的顶点对
(i, j)
以及所有的中间节点k
, 因此时间复杂度为 O ( V 3 ) O(V^3) O(V3).
- 如果
-
检测负权重环: 在某些应用场景下, 可能还需要检测图中是否存在负权重环. 可以在上述步骤完成后进行一次额外的遍历, 检查是否有任何顶点
i
满足D[i][i] < 0
的情况, 如果有, 则说明图中存在负权重环.
样例
给定一个无向带权图, 求任意两个顶点的最短路径.
用 Floyd-Warshall 算法解决这个问题. 因为矩阵运算非常直白, 没有分步骤演示的必要, 直接给出结果:
C++实现
算法实现起来非常直白, 下面是核心部分代码:
其中 D
为邻接矩阵, D[i][j]
表示从顶点 i
到顶点 j
的最短路径长度.
void FloydWarshall() {
auto n = D.size();
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (D[i][k] + D[k][j] < D[i][j]) {
D[i][j] = D[i][k] + D[k][j];
}
}
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
评论记录:
回复评论: