单源最短路问题
在带权图 G=(V,E) 中,每条边都有一个权值 wi,即边的长度。路径的长度为路径上所有边权之和。单源最短路问题是指:求源点 s 到图中其余各顶点的最短路径。
如果用我们之前学习的 dfs 来解决单源最短路问题,效率上会很慢,能解决的问题的数据规模非常小。
而 bfs 能解决的最短路问题只限制在边权为 1 的图上。对于边权不同的图,利用 bfs 求解最短路是错误的。
解决单源最短路径问题常用 Dijkstra 算法,用于计算一个顶点到其他所有顶点的最短路径。Dijkstra 算法的主要特点是以起点为中心,逐层向外扩展(这一点类似于 bfs,但是不同的是,bfs 每次扩展一个层,但是 Dijkstra 每次只会扩展一个点),每次都会取一个最近点继续扩展,直到取完所有点为止
非负权图 , 贪心思想
算法流程
- 一直维护一个还没有确定最短路的点的集合, 每次从集合中选出一个最小的点去更新其他的点
- 不能处理负权图: 贪心正确的前提是没有负权
我们定义带权图 G 所有顶点的集合为 V,接着我们再定义已确定从源点出发的最短路径的顶点集合为 U,初始集合 U 为空,记从源点 s 出发到每个顶点 v 的距离为 dist_v,初始 dist_s=0,其他dist_v为∞。接着执行以下操作:
- 从 V−U 中找出一个距离源点最近的顶点 v,将 v 加入集合 U。
- 并用 dist_v 和点 v 连出的边来更新和 v 相邻的、不在集合 U 中的顶点的 dist,这一步称为松弛操作。
- 重复步骤 1 和 2,直到 V=U或找不出一个从 s 出发有路径到达的顶点,算法结束。
如果最后V !=U,说明有顶点无法从源点到达;否则每个 dist_i表示从 s 出发到顶点 i 的最短距离。
Dijkstra 算法的时间复杂度为O(V^2),其中 V 表示顶点的数量。
1 |
|
再回顾一下 Dijkstra 算法的核心思想,就是一直维护一个还没有确定最短路的点的集合,然后每次从这个集合中选出一个最小的点去更新其他的点。
堆优化
如果每次暴力枚举选取距离最小的元素,则总的时间复杂度是 $\mathcal{O}(V^2)$。
结合之前学习的数据结构,如果考虑用堆优化,用一个set来维护点的集合,这样的时间复杂度就优化到了 $\mathcal{O}((V+E)\log V)$,对于稀疏图的优化效果非常好。
小根堆优化的 Dijkstra 示例代码如下:
1 |
|
算法演示
接下来,我们用一个例子来说明这个算法。
初始每个顶点的 dist 设置为无穷大 inf,源点 M 的 dist_M 设置为 0。
- 当前U=∅,V−U 中 dist 最小的顶点是 M。
- 从顶点 M 出发,更新相邻点的 dist。
更新完毕,此时 U={M},
- V−U 中 dist 最小的顶点是 W。
- 从 W 出发,更新相邻点的 dist。
更新完毕,此时 U={M,W},
- V−U 中 dist 最小的顶点是 E。
- 从 E 出发,更新相邻顶点的 dist。
更新完毕,此时 U={M,W,E},
- V−U* 中 dist最小的顶点是 X。
- 从 X 出发,更新相邻顶点的 dist。
更新完毕,此时 U={M,W,E,X},
- V−U* 中 dist 最小的顶点是 D。
- 从 D 出发,没有其他不在集合 U 中的顶点。
此时U=V,算法结束,单源最短路计算完毕。





