LeetCode Logo

分享|图上单源最短路径的 Dijkstra 算法简易推导及练习

4288
2024.05.18
2024.06.28
发布于 安徽

图的基本概念及术语

Screenshot 2024-06-16 at 9.00.42 AM.png

读者可能不熟悉图论基础知识的一些符号,特此解释一部分。

突然接收这么多知识确实有些挑战,跟着学术一点的图论教程逐步学到这里会好很多,具体可参考《图论入门》。

  • :图
  • : 点集, 在上图中,
  • :边集
  • : 权重函数,上图中边上的数字,比如 。读者如果不熟悉图理解为边的长度即可。
  • : 任意
  • : 从点 到点 的边,一般表示能从 走向 。
  • : 属于

所以 表示边集中任意边的权重 。

其他的相关符号还有:

  • 或者 表示集合 的 size
  • 表示图中 两点的距离(最短路径的权重和)。
  • 表示向点集 中加入点 。
  • 表示邻接表, 上图中 。存储从各点出发的可直达点以及权重,但在学术中权重用权重函数 𝑤 来表达。

最短路径

最短路径 (shortest paths) 的相关实际场景比较广泛,比如地图、网络等。

单源最短路径 (SSSP / single-source shortest paths) 是求解给定某一源点到其所有可达点的最短路径,即使得这些无权路径的边数或者带权路径的权重和最小。

Dijkstra 算法解决的是非负权图的 SSSP,未使用堆查找优化时,也被称为 Dijkstra 暴力算法。

Dijkstra 发音 /ˈdaɪkstrə/,译作“迪杰斯特拉“。

松弛 (Relax)

"松弛"这个术语出现得较多,含义同数学意义上的松弛相同,减少声明成立的约束条件。图的两点之间存在多条路径,找到最短的一条需要比较,每比较一次就减少一次约束。

但我认为此处从数学中沿用这个命名并不好。

introduction1.png
上图表示在 SSSP 中,忽略原图中的其他点和边,探索过程中某一时刻点 A 对其邻接点的松弛。

红框中的下标:

  • 第一个:在当前探索范围内,源点到该点的的距离。
  • 第二个:相应路径上的父节点。和各顶点一样,都是实际以数字存储。A 即 0, B 即 1,以此类推,-1 表示没有父节点。

观察 A B 两点状态,3 + 1 < 5,说明 A 点所处路径向 B 延伸后比此前源点到 B 的路径更短,松弛有效。

同理可得对 C 松弛有效,对 D 松弛无效。

如果之后某刻 A 点再次被有效松弛了,那么应该继续松弛 B C D 点。

原理

dijkstra1.png

如上图所示,给定图 。 为源点,求其到各可达点的最短路径。

设红框区域中的点集为 ,表示 中前 个从 出发最近的点。

记某时刻 。这与 Prim 算法很相似。

从 外的可直达点 中选择离 最近的 点,记录相应路径 和其长度。

从 外的不可直达点 中任选一点记为 ,路径 上一定至少经过 或 。记该点为 ,路径为 。

既然 ,则

根据 点的选择条件可知 ,结合上式可得

所以 为 即 中离 最近的点,此前记录的 为最短路径。

dijkstra2.png

如上所示,令 。再执行上一步,逐渐扩张即可找到 到所有可达点的最短路径。

初始

Dijkstra 算法和 Prim 算法相比,取得周围最近点的思路有所变化,详见下面的算法实现。

PPT 求解展示

松弛部分被我抽离为下方这个函数,表示松弛点 的所有邻接点。

  • 为数组 Distances,表示在当前探索范围内,源点到各点的距离。
  • 为数组 Parents,表示相应路径上的父节点。
  • 和 的数组下标表示顶点。

dijkstra3.png

读者在初次接触伪代码时会不习惯。但据笔者的调查反馈,习惯后体验还是不错的。
在下方的代码中, 表示长度为 ,元素都为 的数组。具体的术语和符号均在《图论入门》的附录中有介绍。

1 / 4

记源点只通过已知的 到周围可直达点的最短距离和路径为 。

可得如果某一次松弛前,我们已经确定了 和 ,那么我们可根据以上证明取 外离源点最近的一点纳入 ,而且松弛该点的邻接点后就可以更新 。

完整代码

code1.png

思考为什么松弛前不用判断点 的邻接点是否 settled?

答:因为处于 settled 的点已经找到最短距离,松弛会一直无效。

除 Settled 外还有一些常见的命名,如 Visited、Done、Used。从语义上来看,Settled 和 Done 更加合适一些。

在很多只考虑距离的应试题中, 的更新可以简写为 。

复杂度

时间:
空间:

与 Prim 算法的复杂度分析大体相同,但本章考虑不可达导致提前截止的情况,所以时间复杂度不是 。还可以用堆查找优化,但实现稍复杂,这在《图论进阶》中有讲到。

练习

给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 successProb[i]。

指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。

如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。

示例1:

v2-2cfa8c6c25011ccb5cc0024ce68652b1_1440w.webp

输入:n = 3, edges = [[0,1],[1,2],[0,2]], successProb = [0.5,0.5,0.2], start = 0, end = 2
输出:0.25000
解释:从起点到终点有两条路径,其中一条的成功概率为 0.2 ,而另一条为 0.5 * 0.5 = 0.25

求解:

Dijkstra 算法中的权重累计方式是相加,秉承非递减原则,最短路径上的权重和最小。

本题的权重累计方式是相乘,但权重(概率),秉承非递增原则,求解概率最大的路径。

所以可以镜像转换松弛过程,代码如下所示。

Screenshot 2024-06-22 at 11.48.33 PM.png

根据原题中的提示部分(未展示在这里),可知该图是稀疏图,所以主流解法是二叉堆查找优化的版本。但这不属于内地本科的教学范围,在面试中也经常不太苛刻。

二叉堆查找优化正好属于《图论进阶》 中免费的试读部分,读者有兴趣可以结合《图论入门》中 Prim 算法的两种堆查找优化看一下。

读者如有困惑尽管指出。也欢迎提出写法建议,不甚感谢!

评论 (0)