最短路径与拓扑排序

2070 字
10 分钟
最短路径与拓扑排序

最短路径与拓扑排序#

最短路径三种算法#

BFS(无权图)#

BFS 求无权图单源最短路径利用其逐层扩展的特性:首次到达即最短。详见 图的遍历

指标
适用场景无权图(边权均为 1)
时间复杂度O(V+E)(邻接表)/ O(V2)(邻接矩阵)
数据结构队列
原理逐层扩展,层数即距离

Dijkstra 算法(非负权图)#

核心思想#

Dijkstra 采用贪心策略:每轮从尚未确定最短路径的顶点中选出距离源点最近的顶点,将其最短路径确定下来,然后用该顶点去松弛其邻接顶点。

维护三个数组:

  • dist[]:源点到各顶点的当前最短距离
  • visited[]:标记顶点最短路径是否已确定
  • path[]:记录前驱顶点,用于回溯完整路径

核心操作——松弛(Relaxation)

若 dist[u] + w(u,v) < dist[v],则更新 dist[v] = dist[u] + w(u,v)

算法过程#

  1. 初始化:dist[src] = 0,其余 dist[i] = INFvisited[] 全部 false
  2. 重复 V 次:
    • 从未访问顶点中选出 dist 最小的顶点 u,标记为已访问
    • 对 u 的所有邻接顶点 v 执行松弛
  3. 算法结束后 dist[] 即为最短距离

复杂度与适用条件#

实现方式时间复杂度适用场景
邻接矩阵 + 简单遍历O(V2)稠密图
邻接表 + 最小堆O(E log V)稀疏图

重要限制:Dijkstra 不能处理含负权边的图。因为一旦顶点被标记为已访问,其 dist 值就不会再更新,但后续可能通过负权边找到更短的路径。

易错:Dijkstra 不能处理负权边,但可以处理带 0 权边的图。408 选择题中”负权”和”非正权”是不同概念。

Floyd 算法(多源最短路径)#

核心思想#

Floyd 算法通过逐步引入中转顶点来求出任意两点间的最短路径,本质是动态规划。

状态转移方程

dist(k)[i][j] = min(dist(k-1)[i][j], dist(k-1)[i][k] + dist(k-1)[k][j])

其中 dist(k)[i][j] 表示仅允许经过顶点 0~k 作为中转时 i 到 j 的最短路径。

算法过程#

  1. 初始化:将邻接矩阵复制到 dist[][]dist[i][i] = 0,无边则 INF
  2. 三重循环:最外层枚举中转顶点 k,内两层枚举所有顶点对 (i, j)
  3. dist[i][k] + dist[k][j] < dist[i][j],则更新
for (int k = 0; k < V; k++) // k 必须在最外层
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
if (dist[i][k] + dist[k][j] < dist[i][j])
dist[i][j] = dist[i][k] + dist[k][j];

复杂度与适用条件#

指标
时间复杂度O(V3)
空间复杂度O(V2)
负权边支持(但不能有负权回路)

易错:三重循环的顺序必须是 k 在最外层。如果把 k 放在内层(如 i-j-k),算法就是错的——因为松弛 dist[i][j] 时,dist[i][k] 和 dist[k][j] 可能尚未被当前轮次更新到最优值。这是 408 选择题的经典陷阱。

三种算法对比#

对比项BFSDijkstraFloyd
问题类型单源最短路径单源最短路径多源最短路径
适用图无权图非负权图任意图(无负环)
时间复杂度O(V+E)O(V2) / O(E log V)O(V3)
空间复杂度O(V)O(V)O(V2)
算法思想逐层扩展贪心动态规划
负权边N/A不支持支持
考研考查算法设计手动模拟填表三重循环顺序陷阱

DAG 描述表达式#

有向无环图(DAG, Directed Acyclic Graph)是表示含有公共子表达式的代数表达式的有效工具。

二叉树 vs DAG:二叉树中重复的子表达式存储多份;DAG 中公共子表达式只需存储一次,通过多个父结点共享。

以表达式 (x+y)((x+y)/x) 为例,DAG 只需 5 个顶点(x, y, +, /, *),而二叉树需要更多结点。

构造方法:自底向上合并重复结点——相同操作数的叶子只保留一个;运算符相同且左右子结点都相同的内部结点合并为一个。

易错:DAG 描述表达式是图论的应用,不要和”二叉树表示表达式”搞混。前者重点是共享子结构减少空间,后者重点是用栈计算表达式的值。


拓扑排序#

前提与定义#

拓扑排序的前提:图必须是有向无环图(DAG)。若图中存在环,则不存在拓扑序列。

  • AOV 网(Activity On Vertex):用顶点表示活动、有向边表示活动间先后关系的有向图
  • 拓扑序列不唯一:一个 DAG 通常有多个合法的拓扑序列

Kahn 算法(BFS 入度法)#

  1. 计算所有顶点的入度,将所有入度为 0 的顶点入队
  2. 队列非空时,取出队首顶点并输出
  3. 将该顶点的所有邻接顶点入度减 1,若入度变为 0 则入队
  4. 重复直到队列为空

环检测:若最终输出的顶点数 < n,则图中存在环(环中顶点入度永远不会减为 0)。

DFS 逆后序法#

对每个未访问顶点执行 DFS,在顶点的所有后继都访问完毕回退时将该顶点压入栈,最终栈顶到栈底即为拓扑序列。

DFS 环检测(三色标记法):若 DFS 过程中遇到状态为”访问中”的顶点,说明存在环。

复杂度分析#

算法时间复杂度空间复杂度
Kahn 算法(BFS 入度法)O(V+E)O(V)
DFS 逆后序法O(V+E)O(V)

关键路径(AOE 网)#

核心概念#

AOE 网(Activity On Edge):用有向无环图表示工程,顶点表示事件(Event),有向边表示活动(Activity),边的权值表示活动持续时间。

  • 源点:入度为 0 的顶点(工程开始)
  • 汇点:出度为 0 的顶点(工程结束)
  • 关键路径:从源点到汇点的最长路径,决定工程最短工期
  • 关键活动:最早开始时间等于最晚开始时间的活动(时间余量为 0)

四个时间参数#

符号含义计算方式
ve(j)事件 j 的最早发生时间ve(j) = max{ ve(i) + w(i,j) },取所有前驱的最大值
vl(j)事件 j 的最晚发生时间vl(j) = min{ vl(k) - w(j,k) },取所有后继的最小值
e(i)活动 ai 的最早开始时间e(i) = ve(该活动的起点)
l(i)活动 ai 的最晚开始时间l(i) = vl(该活动的终点) - w(ai)

关键活动:满足 e(i) == l(i) 的活动。

求解步骤#

  1. 对 AOE 网进行拓扑排序,同时按拓扑序计算 ve[]
  2. 逆拓扑序计算 vl[](初始化 vl[汇点] = ve[汇点])
  3. 遍历每条活动计算 e[] 和 l[]
  4. 找出所有 e(i) == l(i) 的活动,即为关键活动

关键性质#

  • 关键路径可能不唯一,所有关键活动构成的路径都是关键路径
  • 缩短工期只能通过缩短关键活动的持续时间来实现
  • 缩短某关键活动后,该路径可能不再是关键路径(其他路径变为最长),需要重新计算
  • 只有缩短所有关键路径上公共的关键活动,才能确保工期缩短

易错:关键路径是最长路径,不是最短路径——这和最短路径问题的思路恰好相反。

AOV 网 vs AOE 网#

对比项AOV 网AOE 网
顶点活动事件
活动间的先后关系活动(带权)
关注点活动执行的先后顺序工程的最短完成时间
核心算法拓扑排序关键路径

考研高频考点#

  • Dijkstra 手动模拟执行过程,填写 dist[] 表格
  • Dijkstra 不能处理负权边的原因
  • Floyd 三重循环顺序(k 在最外层)
  • Floyd vs Dijkstra 对比
  • 给定 DAG 写出所有可能的拓扑序列
  • 拓扑排序判断有向图是否有环
  • 手算 ve[]、vl[]、e[]、l[] 四个时间参数
  • 根据 e(i) == l(i) 判断关键活动、找出关键路径
  • 关键路径不唯一时缩短工期的条件分析
  • AOV 网 vs AOE 网的区别

关联页面#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

最短路径与拓扑排序
https://lingluoa.icu/posts/shortest-path-topology/
作者
lingluoa
发布于
2026-07-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
lingluoa
Hello, I'm lingluoa.
公告
欢迎来到我的博客!不定期更新中。
文章目录
标签
站点统计
文章
53
分类
9
标签
75
总字数
175,771
运行时长
0
最后活动
0 天前
站点信息
构建平台
ESA Pages
博客版本
Firefly v6.15.6
文章许可
CC BY-NC-SA 4.0