是一种贪心策略,通过不断拓展已知的最短路径范围,逐步为每个顶点确定最短距离;基本思想可以概括为以下四个要点:
一、Dijkstra 型标号法
1. 两种标号
- 永久标号:表示从起点到该顶点的最短路径长度已经确定,不再改变。
- 临时标号:表示从起点到该顶点的当前已知最短路径长度(上界),后续迭代中可能被更新。
2. 核心步骤(迭代更新)
- 初始化:起点获得永久标号 00,其余所有顶点获得临时标号 ∞∞。
- 选择 :在所有临时标号中,找出值最小的顶点。根据贪心策略,该值即为该顶点的最短路径长度,将其从临时标号改为永久标号。
- 更新 :检查刚刚获得永久标号的顶点,遍历它的所有邻接点。如果通过该顶点到达邻接点的路径比邻接点当前的临时标号更短,就更新该邻接点的临时标号(即"松弛"操作)。
- 终止:重复"选择"和"更新"步骤,直到所有顶点都获得永久标号(或找到了目标点的最短路径)。
3. 核心性质(适用条件)
该思路能够成立的关键在于图中边的权重必须为非负数。
- 因为每次选出的临时标号最小值一旦被转为永久,就不可能通过后续其他顶点(距离更长)绕路来获得更短路径。
- 如果存在负权边,这种方法会失效,此时需要采用 Bellman-Ford 算法(贝尔曼-福特算法)的思路。
4. 本质思想
这是一种按路径长度递增的顺序产生最短路径的方法。算法并不是一次性求出所有最短路径,而是先找到离起点最近的点,然后利用它去"松弛"周围更远的点,就像水波从起点一圈一圈向外扩散一样,直到覆盖整个网络。
举例说明:
下面通过一个简单的例子来演示标号法(Dijkstra算法)的具体过程。
示例图
我们考虑一个有向图,顶点为 A、B、C、D,A 为起点。边的方向和权重如下:
- A → B:4
- A → C:2
- B → C:1
- B → D:5
- C → D:8
(所有权重均为非负数,满足算法使用条件。)
标号法步骤
1. 初始化
- 起点 A 获得永久标号:0
- 其余顶点临时标号:B(∞)、C(∞)、D(∞)
- 已永久顶点集合:{A}
2. 第一次迭代
- 从临时标号中选出最小值:目前只有 A 有永久标号,我们需从 A 的邻接点开始更新。
- 实际算法中,每次选当前临时标号最小的顶点转为永久。初始时,只有 A 有永久标号,我们先利用 A 更新邻接点,然后选出临时标号最小的点。
- 更新 :从 A 出发,可到达 B 和 C。
- 到 B:min(∞, 0+4) = 4(临时)
- 到 C:min(∞, 0+2) = 2(临时)
- 现在临时标号:B(4)、C(2)、D(∞)
- 选出临时标号最小的顶点:C(2),将其改为永久标号。已永久:A(0)、C(2)
3. 第二次迭代
- 以 C 为当前永久顶点,更新其邻接点。
- C 可到达 D:min(∞, 2+8) = 10(临时)
- 现在临时标号:B(4)、D(10)
- 选出最小的临时标号:B(4),将其改为永久。已永久:A(0)、C(2)、B(4)
4. 第三次迭代
- 以 B 为当前永久顶点,更新其邻接点。
- B 可到达 C 和 D。
- 到 C:C 已有永久标号 2,且 4+1=5 > 2,不更新。
- 到 D:min(10, 4+5) = 9(更新临时标号为 9)
- 现在临时标号:D(9)
- 选出最小的临时标号:D(9),将其改为永久。已永久:A(0)、C(2)、B(4)、D(9)
5. 结束
所有顶点均获得永久标号,结果如下:
- A → A:0
- A → C:2
- A → B:4
- A → D:9(路径 A→B→D,总长 4+5=9,比直接 A→C→D 的 2+8=10 更短)
二、 Bellman-Ford 型标号法
1. 算法步骤
这种算法不追求一次确定顶点距离,而是反复执行松弛操作:
- 初始化所有标号为 ∞,起点为 0
- 重复 n−1 轮(n 为顶点数),每轮对所有边进行松弛
- 第 k 轮结束后,标号表示从起点出发经过不超过 k 条边的最短距离
- 它能处理负权边,并能检测负权环
2. 举例说明
用以下边(起点 A):
- A→B:3
- A→C:5
- B→C:1
- B→D:2
- C→B:-1 (负权但不成负环,因为 B→C→B = 1+(-1)=0)
- C→D:6
依然有负权边,但无负环。
初始化
dist = [A:0, B:∞, C:∞, D:∞]
第1轮
-
A→B(3): 0+3=3 → B=3
-
A→C(5): 0+5=5 → C=5
-
B→C(1): 3+1=4 < 5 → C=4
-
B→D(2): 3+2=5 → D=5
-
C→B(-1): 4+(-1)=3 = 3 → 不更新
-
C→D(6): 4+6=10 > 5 → 不更新
结果:
[0,3,4,5]
第2轮
-
A→B: 0+3=3 = 3 不更新
-
A→C: 5 > 4 不更新
-
B→C: 3+1=4 = 4 不更新
-
B→D: 3+2=5 = 5 不更新
-
C→B: 4+(-1)=3 = 3 不更新
-
C→D: 4+6=10 > 5 不更新
无变化,提前结束(也可继续至第3轮)。
第2轮无更新,算法终止。最终最短距离:A:0, B:3, C:4, D:5。
问题1:为什么不能存在负权环?
负权环使最短路径失去定义,如果图中存在一个环,且环上所有边的权值之和为负数,那么这个环称为负权环 。由于可以在环上无限次绕行,每绕一圈路径总长度就会减少,因此从起点到环上任意一点(或环上可到达的点)的路径长度可以无限减小,不存在"最短"路径(通常定义为负无穷)。
问题2:为什么最多只需要执行V-1次?
- 第一轮松弛后,
dist[v]是只使用 1 条边 能到达 vv 的最短距离(即直接边)。 - 第二轮松弛时,我们可以利用第一轮的结果,通过"在一条边的基础上再接一条边"来构造长度为 2 的路径,并与已有距离比较,从而得到最多 2 条边的最短距离。
- 依次类推,第 kk 轮结束后,我们实际上已经考虑了所有不超过 kk 条边的路径。
由于最短路径(无负环)不需要超过 ∣V∣−1∣V ∣−1 条边,所以 ∣V∣−1∣V∣−1 轮足以找到所有顶点的最短距离