Dijkstra 算法详解:贪心策略构建最短路径树

> 本文从零开始讲解 Dijkstra 算法的核心思想------贪心策略与最短路径树,并通过一个完整的例子手把手带你推演每一步的距离更新,彻底理解"为什么这样操作就能找到所有节点的最短路径"。

一、算法定位与问题定义

Dijkstra 算法 由荷兰计算机科学家 Edsger W. Dijkstra 于1956年提出,用于解决单源最短路径 问题,是图论中最经典的算法之一。它的本质是贪心算法,逻辑上无法脱离贪心,不存在所谓"非贪心版 Dijkstra"。

问题描述

  • 给定一张图 G = (V , E ),共有 v 个节点,e 条边(可以是无向或有向图)
  • 每条边带有一个非负的权值(称为距离、权重或开销)
  • 指定一个源点 s ,求从 s 到图中所有其他可达节点的最短路径及其长度

⚠️ 注意 :Dijkstra 算法要求所有边的权重非负。如果存在负权边,贪心策略将失效,需要使用 Bellman-Ford 等算法。

二、核心思想:贪心地构建最短路径树

Dijkstra 算法的精髓在于:按照与源点距离从小到大的顺序,逐个确定每个节点的最短路径,并在确定后不再更改 。整个过程可以看作是在以 s 为根,逐步"生长"出一棵最短路径树

为什么贪心是正确的?

想象我们从 s 出发探索整张图:

  1. 第一个最近节点
    s 最近的点,必然在直接与 s 相连的节点中。
    任何不直接相连的点,都必须先经过某个直接邻居才能到达,其距离不可能小于最短的那条直连边。
    因此,直接邻居中边权最小的那个节点,就是全图中距离 s 最近的点,记作 v 1,它的最短距离在此刻就已经确定。
  2. 第二个次近节点
    次近的点可能是另一个直接邻居,也可能是通过 v 1 才能到达的点。
    但它不可能 经过其他尚未确定的点------那些点本身比 v 1 更远,再绕路只会让距离更长。
    所以,我们只需在「直接邻居」和「v 1 的邻居」这些尚未确定的节点中,选一个总距离最小的,它就是次近点 v 2
  3. 依次类推
    每当我们确定一个节点,就把它加入"已确定集合",并用它去尝试更新它的邻居的距离(即松弛操作 )。
    之后,从未确定节点中再次选出距离最小的那个,它的最短路径就可以确定下来。

如此反复,就像是从根 s 开始,一圈一圈向外扩展,每次选出最近的一个新节点挂到树上。这棵树最终包含 s 和所有可达节点,树上从根到任意节点的路径,就是该节点的最短路径。

最短路径树

  • 以源点 s 为根
  • 每个节点通过唯一的前驱边连接到树中
  • 树的节点集合 = 所有从 s 可达的节点
  • 树上 sv 的路径权重和 = sv 的最短距离

三、算法步骤

用伪代码描述如下:

复制代码
1. 初始化
   - 距离数组 dist[s] = 0,其他节点 dist[v] = ∞
   - 已确定最短路径的节点集合 S = ∅
   - 前驱数组 prev[v] = null

2. 循环,直到所有节点都被确定或最小距离为 ∞(不可达)
   - 从未确定节点中,选择 dist 值最小的节点 u
   - 将 u 加入 S(此时 dist[u] 就是最终的最短距离)
   - 对 u 的每个邻居 v:
     - 若 dist[u] + weight(u, v) < dist[v]:
       - 更新 dist[v] = dist[u] + weight(u, v)
       - 设置 prev[v] = u

3. 结束
  循环直到:所有可达节点都已被确定,或者在某次迭代中选出的最小距离节点,其 dist 为 ∞ (这表明剩余节点全部不可达)。

四、实例演算

我们用一张具体的图来完整走一遍算法,图中的节点和边权如下:

起点 终点 边权(距离)
0 1 2
0 2 6
0 3 7
1 3 3
1 4 6
2 4 1
3 4 5

源点:0,目标:求出 0 到 1、2、3、4 的最短路径。

4.1 初始状态

  • dist[0] = 0
  • dist[1] = dist[2] = dist[3] = dist[4] = ∞
  • 已确定集合 S = {0},未确定集合 U = {1,2,3,4}

4.2 逐步推演

第1步:从 0 开始松弛邻居
  • 更新邻居距离:
    • 0→1:0+2 = 2 < ∞ → dist[1] = 2
    • 0→2:0+6 = 6 < ∞ → dist[2] = 6
    • 0→3:0+7 = 7 < ∞ → dist[3] = 7
    • dist[4] 仍为 ∞
  • 当前 dist = [0, 2, 6, 7, ∞]
  • 选出最小未确定节点:1(距离=2)
  • 加入 S:S = {0,1},U = {2,3,4}
第2步:用节点 1 松弛邻居
  • 1 的邻居:3、4
    • 1→3:dist[1] + 3 = 2+3 = 5 < 7 → 更新 dist[3] = 5
    • 1→4:dist[1] + 6 = 2+6 = 8 < ∞ → 更新 dist[4] = 8
  • 当前 dist = [0, 2, 6, 5, 8]
  • 选出最小未确定节点:3(距离=5)
  • 加入 S:S = {0,1,3},U = {2,4}
第3步:用节点 3 松弛邻居
  • 3 的邻居:4
    • 3→4:dist[3] + 5 = 5+5 = 10 > 8 → 不更新
  • 当前 dist = [0, 2, 6, 5, 8]
  • 选出最小未确定节点:2(距离=6)
  • 加入 S:S = {0,1,3,2},U = {4}
第4步:用节点 2 松弛邻居
  • 2 的邻居:4
    • 2→4:dist[2] + 1 = 6+1 = 7 < 8 → 更新 dist[4] = 7
  • 当前 dist = [0, 2, 6, 5, 7]
  • 选出最小未确定节点:4(距离=7)
  • 加入 S,U 变空,算法结束。

五、几个关键点的解释

  1. 松弛操作
    公式 dist[v] > dist[u] + weight(u,v) 的意义:检查"经过 u 到 v"是否比已知的到 v 的路径更短,如果是,就更新。
    例如本例中:
    • dist[3] = 7 > dist[1] + edge[1][3] = 5 → 更新为5
    • dist[4] = 8 > dist[2] + edge[2][4] = 7 → 更新为7
  2. 贪心选择的无后效性
    每次选择的节点距离一定是最短的,因为后续只可能通过已确定节点到达其他节点,而边权非负,所以距离不会再变小。
  3. 负权边为什么不行
    如果图中存在负权边,可能会在后续发现一条绕远的路径反而总距离更短,从而推翻之前"已确定"的最短距离,贪心策略不再成立。

六、总结

  • Dijkstra 算法的核心是贪心策略,每次从未确定节点中取出距离最小的节点,确定其最短路径,并用它松弛邻居。
  • 整个过程等价于在图中构建一棵以源点为根的最短路径树
  • 算法实现简单,时间复杂度 O (V 2),使用优先队列可优化至 O ((V +E ) log V)。
  • 限制:所有边的权重必须非负。

掌握 Dijkstra 算法,不仅能够解决单源最短路径问题,更能够深入理解"贪心正确性"的经典范例。希望本文的逐步演算能帮你彻底吃透它!


相关推荐
广州服务器托管1 小时前
[2026.5.12][IT工坊]WIN11.26300.8376专业工作站版[PIIS]中简 深度优化
运维·人工智能·windows·计算机网络·可信计算技术
小明同学012 小时前
计算机网络编程---UDP客户端与服务端
网络协议·计算机网络·udp
剑锋所指,所向披靡!2 小时前
计算机网络的数据链路层
网络·计算机网络
如君愿2 小时前
考研复习 Day 35 | 习题--计算机网络 第七章 网络安全(上)、数据结构 排序算法(上)
数据结构·计算机网络·考研·课后习题
MandalaO_O2 小时前
Web 开发:计算机网络知识梳理
前端·网络·计算机网络
艾莉丝努力练剑3 小时前
【Linux网络】Linux 网络编程:应用层自定义协议与序列化(3):网络计算器实现和守护进程
linux·运维·服务器·网络·c++·计算机网络·安全
@encryption3 小时前
计算机网络 --- RSTP,MSTP
服务器·网络·计算机网络
谷雨不太卷1 天前
计算机网络:套接字
linux·服务器·计算机网络
黄昏回响1 天前
信息系统基础知识(六):办公自动化系统(OAS)详解
计算机网络·程序人生·面试·自动化·改行学it