单源最短路径 多源最短路径
参考链接:
单源最短路径
单源最短路径问题(Single-Source Shortest Path Problem) 是图论中的一个经典问题,目的是在一个加权图中,从一个指定的起点出发,计算到其他所有顶点的最短路径长度。这里的图可以是有向图或无向图,边的权值通常是非负的。该问题广泛应用于路由算法、网络优化、地理信息系统(GIS)等领域。
问题定义
给定一个加权图 $G(V,E)$,其中 $V$ 表示顶点集,$E$ 表示边集。每条边 $(u,v)$ 具有一个非负的权重 $w(u,v)$,目标是计算从一个源点 $S$ 到图中所有其他顶点的最短路径。
常见算法
- Dijkstra 算法:用于图中所有边的权重为非负数时的单源最短路径问题。
- Bellman-Ford 算法:能够处理带负权边的情况,并且可以检测负权环,但时间复杂度较高。
- Floyd-Warshall 算法:用于求解任意两点之间的最短路径,适用于所有顶点之间的最短路径问题,虽然也可以处理负权边,但不适用于负权环。
这里,我们主要介绍 Dijkstra 算法和Bellman-Ford 算法,它是解决单源最短路径问题的最常用方法,适用于没有负权边的图。
Dijkstra 算法
Dijkstra 算法基于贪心策略,逐步确定最短路径。在每一步,它选择当前距离源点最近的未访问节点,并更新与其邻接的所有节点的最短路径。
算法步骤
- 初始化:设定源点的距离为 0,其他所有节点的距离为无穷大。将源点加入一个未处理的集合。
- 在未处理节点中选择距离源点最近的节点。
- 更新该节点的所有邻居的最短路径。如果通过当前节点到达某个邻居的路径更短,就更新该邻居的最短距离。
- 标记当前节点为已处理,表示其最短路径已确定。
- 重复步骤 2 和 3,直到所有节点都被处理完毕。
C++实现
以下是 Dijkstra 算法的 C++ 实现,使用了优先队列(priority_queue
)来高效地选择距离源点最近的节点:
1 |
|
代码解释:
- 输入部分:
n
是图的顶点数,m
是边的数量。- 每条边由三个整数表示:
u
、v
、w
,分别表示从顶点u
到顶点v
的边,权重为w
。 source
表示计算单源最短路径的起始顶点。
- 图的表示:
- 使用
vector<vector<PII>> graph(n)
来存储图的邻接表,每个元素是一个PII
对,表示一个邻接节点及其边的权重。
- 使用
- Dijkstra 算法:
- 初始化
dist
数组,表示从源点到其他点的最短路径距离,初始值为INT_MAX
,表示不可达。 - 使用优先队列
pq
来动态选择当前最短的未访问节点。 - 对每个节点的邻接节点进行松弛操作,更新最短路径。
- 初始化
- 输出结果:
- 输出源点到每个节点的最短距离。如果某个节点不可达,则输出
INF
。
- 输出源点到每个节点的最短距离。如果某个节点不可达,则输出
时间复杂度:
使用优先队列(堆)实现 Dijkstra 算法时,时间复杂度为
$O((V+E)logV)$
Bellman-Ford 算法简介
Bellman-Ford 算法是一种用于求解单源最短路径问题的经典算法。与 Dijkstra 算法不同,Bellman-Ford 算法不仅可以处理非负权边的图,还能够处理具有负权边的图,甚至能检测图中是否存在负权环。(在图论中,负权环指的是图中一个环(即路径可以回到起点),该环上所有边的权重之和小于零。简单来说,负权环是一个循环路径,沿着该路径走一圈所得到的权重总和是负数。)
算法原理
Bellman-Ford 算法的核心思想是通过逐步“松弛”图中每条边来逐渐逼近最短路径的解。松弛操作指的是,假设当前节点 u
到达节点 v
的路径的已知距离为 d[u]
和 d[v]
,如果通过 u
到 v
的路径会使得 d[v]
的值变小(即 d[u] + w(u,v) < d[v]
),则更新 d[v]
。
算法步骤
- 初始化:
- 对于所有节点
v
,设置初始距离d[v]
为无穷大(表示从源节点到该节点不可达)。 - 对源节点
s
,设置d[s] = 0
,因为源节点到自身的距离为 0。
- 对于所有节点
- 松弛操作:
- 对图中的每一条边
(u, v)
,如果d[u] + w(u, v) < d[v]
,则更新d[v] = d[u] + w(u, v)
。 - 重复上述松弛操作
V - 1
次,其中V
是图中节点的数量。这是因为最长的最短路径可能经过图中最多V - 1
条边。
- 对图中的每一条边
- 负权环检测:
- 在完成
V - 1
次松弛操作之后,若仍然存在边(u, v)
,使得d[u] + w(u, v) < d[v]
,则图中存在负权环。
- 在完成
算法时间复杂度
- 由于每次松弛操作需要遍历所有的边,而最多需要进行
V - 1
次松弛操作,因此时间复杂度为 **O(V * E)**,其中V
是图中的节点数,E
是图中的边数。
优缺点
优点:
- 可以处理包含负权边的图,且能够检测负权环。
- 算法实现相对简单。
缺点:
- 时间复杂度相对较高,尤其是在稠密图中,可能不如 Dijkstra 算法高效。
C++ 实现代码
下面是 Bellman-Ford 算法的 C++ 实现:
1 |
|
代码说明
Edge 结构体:表示图中的一条边,包含起始节点
u
,目标节点v
和边的权重weight
。BellmanFord 函数:
V
表示节点数,E
表示边数,edges
是图中的边。- 在第一次循环中进行
V - 1
次松弛操作,更新最短路径。 - 第二次循环用于检查是否存在负权环,如果发现负权环,则输出提示信息并返回
false
。
main 函数:
- 构建一个包含 5 个节点和 8 条边的有向图,图中包含一些负权边。
调用
BellmanFord
函数计算从源节点 0 出发的最短路径。
输出示例
假设运行该程序,输出结果可能如下:
1 |
|
如上所示,从源节点 0
出发,经过松弛操作后得到每个节点的最短距离。
总结
Bellman-Ford 算法通过反复松弛每条边来找到图中各个节点到源节点的最短路径。它能够处理包含负权边的图,并且具有较强的负权环检测能力。尽管其时间复杂度较高,但在一些特殊情况下(例如图中可能包含负权边或需要检测负权环)是非常有效的算法。
全源最短路径
全源最短路径问题(All-Pairs Shortest Path,简称 APSP)是指给定一个加权图,要求计算图中所有顶点对之间的最短路径长度。与单源最短路径(如 Dijkstra 算法)不同,全源最短路径算法需要计算每一对顶点之间的最短路径。
常见算法
Floyd-Warshall 算法
Floyd-Warshall 算法是一种动态规划算法,用于解决全源最短路径问题。它通过逐步检查所有可能的中间顶点,更新路径的最短值。该算法适用于处理稠密图(即边的数量较多的图),时间复杂度为 $O(V^3)$,其中 V 是图的顶点数。(这个我怎么我感觉我都能想出来🤓)
Johnson 算法
Johnson 算法使用了 Dijkstra 算法来求解全源最短路径。它通过对图进行加权重标定,转化为一组单源最短路径问题。适用于稀疏图(边的数量较少的图),时间复杂度为 $O(V^2 \log V + VE)$,其中 E 是边数,V 是顶点数。
Floyd-Warshall 算法详细介绍
Floyd-Warshall 算法的核心思想是,假设图中存在一个中间顶点 k,并计算从每个顶点 i 到顶点 j 的最短路径。然后通过反复迭代更新最短路径。具体的更新方式是:
$$
d(i, j) = \min(d(i, j), d(i, k) + d(k, j))
$$
其中,$d(i,j)$ 表示从顶点 i 到顶点 j 的最短路径,k 是图中一个中间顶点。
算法流程
- 初始化一个 $V \times V$ 的距离矩阵 dist,其中 $dist[i][j]$表示顶点 i 到顶点 j 的初始距离。如果存在边,则为该边的权重,否则为无穷大。
- 对于每个顶点 k,遍历所有顶点对 i,j,尝试通过顶点 k 来更新 $dist[i][j]$。
- 最终,矩阵 dist 中的值即为所有顶点对之间的最短路径。
C++ 实现
1 |
|
代码说明
- 邻接矩阵初始化:
- 初始化一个 $V \times V $的矩阵
dist
,其中dist[i][j]
代表从顶点 i 到顶点 j 的距离。如果存在边,则设置为该边的权重;否则,设置为INF
(无穷大)。
- 初始化一个 $V \times V $的矩阵
- Floyd-Warshall 算法:
- 通过三层嵌套的循环来遍历每对顶点 i 和 j,对于每个可能的中间顶点 k,更新
dist[i][j]
为更小的值,即min(dist[i][j], dist[i][k] + dist[k][j])
。
- 通过三层嵌套的循环来遍历每对顶点 i 和 j,对于每个可能的中间顶点 k,更新
- 打印结果:
- 最终,
dist
矩阵中存储了每对顶点之间的最短路径。如果某一对顶点之间没有路径,值为INF
。
- 最终,
时间复杂度和空间复杂度
- 时间复杂度:Floyd-Warshall 算法的时间复杂度是$O(V^3)$,因为我们有三重循环,每一重循环都遍历 V 次。
- 空间复杂度:空间复杂度是 $O(V^2)$,用于存储最短路径的矩阵。
适用场景
Floyd-Warshall 算法适用于顶点数量相对较小或者图较为稠密的场景。当图中的边非常多时,它比一些基于优先队列的算法(如 Dijkstra)更高效,尤其是在计算所有顶点对之间的最短路径时。
总结
Floyd-Warshall 算法是解决全源最短路径问题的一种经典算法,适用于求解图中任意两点之间的最短路径。尽管其时间复杂度较高,但在处理小规模的图时,它依然是一个非常有效和直观的选择。