相关笔记

概览

本节介绍在有向无环图(DAG)中求解单源最短路径问题的算法。由于 DAG 不含环,我们可以先对顶点进行拓扑排序,然后按拓扑序依次对每个顶点的出边执行松弛操作。整个过程只需一轮松弛,时间复杂度为 ====,比 Bellman-Ford 的 显著更优。该算法还能自然地处理负权边

要点列表:

  • DAG 中不可能存在负权环(因为 DAG 无环),所以最短路径总是良定义的
  • 利用拓扑序保证:处理顶点 时,从源点到 的最短路径已经确定
  • 只需一轮按拓扑序的松弛,时间复杂度
  • 同一框架可求解 DAG 最长路径(将所有权值取反)

知识结构总览

flowchart TD
    A["22.2 DAG中的单源最短路径"] --> B["问题背景"]
    A --> C["算法流程"]
    A --> D["正确性证明"]
    A --> E["复杂度分析"]
    A --> F["与Bellman-Ford对比"]
    A --> G["应用场景"]

    B --> B1["DAG: 有向无环图"]
    B --> B2["无负权环(因为无环)"]
    B --> B3["允许负权边"]

    C --> C1["步骤1: 拓扑排序 G"]
    C --> C2["步骤2: 初始化 d[s]=0, d[v]=∞"]
    C --> C3["步骤3: 按拓扑序遍历每个顶点"]
    C --> C4["步骤4: 对每个出边执行 RELAX"]

    D --> D1["拓扑序的关键性质"]
    D --> D2["处理u时, s到u的最短路径已确定"]
    D --> D3["松弛出边后, s到后继的最短路径也确定"]

    E --> E1["拓扑排序: O(V+E)"]
    E --> E2["松弛所有边: O(E)"]
    E --> E3["总计: O(V+E)"]

    F --> F1["Bellman-Ford: Θ(VE), |V|-1轮"]
    F --> F2["DAG: O(V+E), 仅1轮"]
    F --> F3["DAG更高效但要求无环"]

    G --> G1["PERT/CPM 项目调度"]
    G --> G2["编译器依赖分析"]
    G --> G3["电路延迟分析"]

核心思想

核心思路

DAG 最短路径算法的关键洞察是:拓扑排序天然给出了一个正确的松弛顺序。在拓扑序中,如果存在边 ,则 一定在 之前被处理。这意味着当我们处理顶点 时,所有可能到达 的路径上的中间顶点都已经被处理过了,因此 已经是最终的最短路径值。接下来对 的所有出边执行松弛,就能正确传播最短路径信息。整个过程只需一轮遍历。

DAG-SHORTEST-PATHS 伪代码

算法执行流程

  1. 拓扑排序:对有向无环图 G 的所有顶点进行拓扑排序,得到线性序列
  2. 初始化:源点 s 距离设为 0,其余顶点距离设为无穷大
  3. 按序松弛:按拓扑序依次遍历每个顶点 u,对 u 的每条出边执行松弛操作
  4. 完成:所有顶点处理完毕后,各顶点的距离值即为最短路径
flowchart TD
    A["对图 G 进行拓扑排序"] --> B["INITIALIZE-SINGLE-SOURCE(G, s)"]
    B --> C["按拓扑序取出下一个顶点 u"]
    C --> D{"还有未处理的顶点?"}
    D -->|是| E["遍历 u 的每条出边 (u,v)"]
    E --> F["RELAX(u, v, w)"]
    F --> C
    D -->|否| G["完成: d[v] = δ(s,v)"]
DAG-SHORTEST-PATHS(G, w, s)
1  topologically sort the vertices of G
2  INITIALIZE-SINGLE-SOURCE(G, s)
3  for each vertex u, taken in topologically sorted order
4      for each vertex v ∈ G.Adj[u]
5          RELAX(u, v, w)

DAG-SHORTEST-PATHS 算法

输入: 带权有向无环图 ,权函数 ,源点

输出: 对每个顶点 值构成最短路径树

算法步骤:

  1. 拓扑排序(第1行): 的顶点进行拓扑排序,得到线性序列
  2. 初始化(第2行): ,其余
  3. 按拓扑序松弛(第3-5行): 按拓扑序依次处理每个顶点 ,对其每条出边 执行 RELAX

正确性证明

定理(DAG-SHORTEST-PATHS 正确性)

是带权有向无环图,源点为 ,则 DAG-SHORTEST-PATHS 算法终止时,对所有顶点 ,有

证明:

关键性质: 在拓扑序中,若存在从 的路径,则该路径上的所有顶点都出现在 之前。

【对拓扑序位置归纳:基础情况(首个顶点无入边)】 我们对拓扑序的位置进行归纳证明:

归纳假设: 在处理完拓扑序中前 个顶点后,对这 个顶点中的每一个

基础情况():

  • 拓扑序中的第一个顶点不可能有入边(否则存在前驱,矛盾)
  • 若该顶点是 ,则
  • 若该顶点不是 ,则不存在从 到它的路径,
  • 归纳假设成立

归纳步骤(): 【最短路径前驱 已由归纳假设确定,松弛

  • 设第 个被处理的顶点为
  • 考虑从 的任意一条最短路径 ,其中 是最后一条边
  • 由于 是 DAG,路径 上的所有顶点互不相同,且 出现在 之前的拓扑序中
  • 由归纳假设,处理完
  • 当处理 时,算法对边 执行了 RELAX,因此:
  • 又由上界性质,
  • 因此
  • 归纳假设成立

终止:

  • 所有顶点处理完毕,对每个顶点

正确性得证。

为什么一轮就够

Bellman-Ford 需要 轮松弛是因为它不知道边的正确处理顺序,只能通过反复迭代来确保信息传播到所有顶点。而 DAG 的拓扑排序提供了一个天然的依赖顺序——沿着拓扑序处理,信息就像水流一样从上游(源点方向)自然流向下游,一轮就足够了。

复杂度分析

时间与空间复杂度

时间复杂度:

  • 拓扑排序(第1行):(使用 DFS 或 Kahn 算法)
  • 初始化(第2行):
  • 按拓扑序松弛(第3-5行):遍历每个顶点一次,对每条出边执行 RELAX,共
  • 总计:

空间复杂度: (存储 数组、 数组和拓扑排序结果)

与 Bellman-Ford 对比:

维度Bellman-FordDAG-SHORTEST-PATHS
时间复杂度
松弛轮数$V
负权边支持支持
负权环检测支持不需要(DAG 无环)
适用范围一般有向图仅 DAG

关键性质:DAG 中不存在负权环

DAG 无负权环

性质: 有向无环图(DAG)中不可能存在任何环,因此自然不可能存在负权环。

推论: 在 DAG 中,从源点 到每个可达顶点 的最短路径 总是良定义的(有限值),不存在”最短路径为 “的情况。

意义: 这使得 DAG 最短路径问题比一般图的最短路径问题更简单——我们不需要检测负权环,也不需要担心最短路径无定义的问题。这是 DAG-SHORTEST-PATHS 算法能够达到 高效性的根本原因之一。

DAG 最长路径

DAG 最长路径

DAG 最长路径问题可以通过权值取反转化为 DAG 最短路径问题:

方法: 定义新的权函数 ,然后对 运行 DAG-SHORTEST-PATHS。设 是在 中的最短路径估计值,则从 的最长路径权重为

注意: 一般图的最长路径问题是 NP 困难的,但在 DAG 中可以在 时间内求解——这是 DAG 结构带来的巨大优势。


补充理解与拓展

PERT/CPM 项目调度——DAG 最短路径的经典应用

PERT(Program Evaluation and Review Technique)CPM(Critical Path Method) 是项目管理中广泛使用的两种方法,它们的核心数据结构就是 DAG:

  • 顶点表示项目中的活动(任务),或表示事件(里程碑)
  • 有向边表示活动之间的先后约束关系(前置依赖)
  • 边权表示活动的持续时间

关键路径(Critical Path): 从项目开始到结束的最长路径(因为并行任务中,最耗时的路径决定了项目的最短完成时间)。通过将权值取反后运行 DAG 最短路径算法,可以高效找到关键路径。

松弛时间(Slack Time): 每个活动的松弛时间等于其最晚开始时间减去最早开始时间。关键路径上的活动松弛时间为0,是项目进度的瓶颈。

PERT/CPM 在建筑工程、软件开发、航空航天等领域的项目管理中有广泛应用,是运筹学最成功的应用之一。

编译器中的依赖分析

在编译器设计中,DAG 最短/最长路径算法有多个重要应用:

  1. Makefile 依赖分析: 构建系统(如 Make)使用 DAG 表示文件之间的依赖关系。顶点是文件,边 表示文件 依赖于文件 。拓扑排序确定编译顺序,最长路径确定整个项目的最小构建时间。

  2. 指令调度: 在编译器后端,指令之间的数据依赖形成 DAG。通过在 DAG 上寻找最长路径(关键路径),编译器可以确定指令级并行的上界,优化流水线利用率。

  3. 模块加载顺序: 在编程语言的模块系统中(如 ES modules、Python imports),模块间的导入关系形成 DAG。拓扑排序确定模块的加载顺序,检测循环依赖。

电路延迟分析

在数字电路设计中,DAG 最长路径算法用于分析组合逻辑电路的关键路径延迟

  • 顶点表示逻辑门(AND、OR、NOT 等)或电路的输入/输出端口
  • 有向边表示信号传播方向
  • 边权表示信号通过该连接的传播延迟

从电路输入到输出的最长路径就是电路的最大延迟,它决定了电路的最高工作频率。这一分析是 ASIC 和 FPGA 设计流程中的标准步骤,直接影响芯片性能。


易混淆点与辨析

误区:DAG 最短路径也需要多轮松弛

错误理解: “DAG 最短路径和 Bellman-Ford 一样,需要多轮松弛才能保证正确性”

正确理解: DAG 的拓扑排序保证了正确的松弛顺序。在拓扑序中,每条边 的起点 总是在终点 之前被处理。因此,当处理 时,从源点到 的最短路径已经确定(因为所有能到达 的路径上的中间顶点都已被处理)。对 的出边松弛后, 的值也被正确设置。整个过程只需一轮遍历所有边。

对比 Bellman-Ford: Bellman-Ford 不利用图的结构信息,盲目地对所有边进行 轮松弛。对于 DAG 来说这是浪费的——拓扑序已经告诉我们只需要一轮。

误区:DAG 最短路径不能处理负权边

错误理解: “DAG 最短路径算法和 Dijkstra 一样,不能处理负权边”

正确理解: DAG-SHORTEST-PATHS 算法可以正确处理负权边。该算法不依赖贪心选择(Dijkstra 的限制来源),而是依赖拓扑序保证的正确性。无论边权是正是负,只要图是 DAG,算法就能正确运行。这是 DAG 最短路径算法相对于 Dijkstra 的重要优势之一。

误区:DAG 最长路径和最短路径完全一样

错误理解: “DAG 最长路径直接用最短路径算法就行,没有任何区别”

正确理解: DAG 最长路径可以通过权值取反转化为最短路径问题,但需要注意以下区别:

  • 转化后运行 DAG-SHORTEST-PATHS 得到的是取反后的最短路径值,需要再取反才能得到原始的最长路径值
  • 前驱子图 记录的是取反后的图中的最短路径,对应原始图中的最长路径, 关系可以直接使用
  • 在实际应用中(如 PERT/CPM),通常需要同时计算最早开始时间(正向最长路径)和最晚开始时间(反向最长路径),需要运行两次算法

误区:拓扑排序的结果是唯一的

错误理解: “DAG 的拓扑排序只有一种结果,所以最短路径算法的结果也是唯一的”

正确理解: DAG 的拓扑排序可能不唯一——当多个顶点之间没有偏序关系时,它们的相对顺序可以任意排列。然而,无论选择哪种拓扑排序,DAG-SHORTEST-PATHS 的最终结果都是相同的)。不同的拓扑排序只影响中间松弛的顺序,不影响最终结果。这类似于 Bellman-Ford 中边的处理顺序不影响最终结果。


习题精选

题号题目描述难度
22.2-1在图24.5的有向图上运行 DAG-SHORTEST-PATHS,以 为源点,展示每步的
22.2-2证明将第3行改为”for the first $V
22.2-3修改 DAG-SHORTEST-PATHS 使其在线性时间内求解带权顶点的 DAG 最长路径问题⭐⭐⭐
22.2-4给出高效算法计算 DAG 中的路径总数⭐⭐

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 15DAG Shortest Pathshttps://www.youtube.com/watch?v=Aa2klY6ljEMMIT 完整讲解,含 PERT/CPM 应用
Abdul BariShortest Path in DAGhttps://www.youtube.com/watch?v=TXkDpqjDMHA逐步动画演示,直观易懂
WilliamFisetDAG Shortest/Longest Pathhttps://www.youtube.com/watch?v=TXkDpqjDMHA含最长路径转化方法
GeeksforGeeksShortest Path in DAGhttps://www.geeksforgeeks.org/shortest-path-dag-weights-adjacency-list-representation/文字+图示详解
NeetCodeGraph Algorithmshttps://www.youtube.com/watch?v=bZkzH5x0SKU实战编程视角

教材原文

CLRS 第4版 22.2节原文(对应第3版24.2节)

Our algorithm for finding shortest paths from a single source in a directed acyclic graph combines topological sorting with a single pass of the RELAX procedure. The algorithm starts by topologically sorting the dag to impose a linear ordering on the vertices. If the dag contains a path from vertex to vertex , then precedes in the topological sort. We then process the vertices in this topologically sorted order. When we process a vertex, we relax each edge that leaves the vertex.

The total running time is linear in , since the topological sort takes time and each of the edges is relaxed exactly once during the single pass over the vertices.

CLRS 第4版 22.2节原文(正确性)

Because we process vertices in topologically sorted order, when we come to process a vertex, all vertices that can reach it have already had their shortest-path estimates set to their final values. The key observation is that when a vertex is processed in the topological order, its distance estimate is already correct. Therefore, we only need to relax each edge once, and the algorithm correctly computes shortest-path weights.


参见Wiki

概念页尚未创建

  • DAG 最短路径概念页待创建
  • 拓扑排序概念页待创建
  • 关键路径(Critical Path)概念页待创建

第22章-单源最短路径 有向无环图中的单源最短路径