news 2026/8/23 23:33:53

拓扑排序详解(Topological Sort)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
拓扑排序详解(Topological Sort)

拓扑排序详解(Topological Sort)

拓扑排序是对有向无环图(DAG)的顶点进行线性排序,使得对于每条有向边u → v,顶点u在排序中都出现在v之前。


一、Kahn 算法(BFS 版本)

算法核心

Kahn 算法的核心是用队列维护一个入度为 0 的节点集合,不断"剥离"这些节点,类似于"剥洋葱"思想。

算法流程
  1. 初始化:统计所有节点的入度,将入度为 0 的节点入队
  2. 循环剥离
    • 从队列取出一个节点u,加入拓扑序列
    • 删除从u出发的所有边(即u的所有邻接点入度减 1)
    • 如果某个邻接点的入度变为 0,将其入队
  3. 判断结果
    • 如果拓扑序列长度等于n,说明存在拓扑排序(无环)
    • 否则,图中存在环,无法拓扑排序
图解示例

假设图如下:

入度统计: 点1:入度0 点2:入度3(来自1、3、4) 点3:入度0 点4:入度0 点5:入度2(来自2、6) 点6:入度1(来自4)

执行过程:

初始队列:[1, 3, 4] 弹出4 → 删除4→2, 4→6 → 点6入度变0 → 队列:[1, 3, 6] 弹出1 → 删除1→2 → 点2入度变2 → 队列:[3, 6] 弹出3 → 删除3→2 → 点2入度变1 → 队列:[6] 弹出6 → 删除6→5 → 点5入度变1 → 队列:[]

这时队列为空,但点2和点5还未输出,说明有环!❌

完整代码
#include<bits/stdc++.h>usingnamespacestd;constintN=100005;vector<int>g[N],tp;intdu[N];// 入度数组intn,m;booltopo(){queue<int>q;// 1. 入度为0的点入队for(inti=1;i<=n;i++){if(du[i]==0)q.push(i);}// 2. 不断删除入度为0的点while(!q.empty()){intu=q.front();q.pop();tp.push_back(u);for(intv:g[u]){du[v]--;// 删除边 u→vif(du[v]==0){q.push(v);}}}// 3. 判断是否有环returntp.size()==n;}intmain(){ios::sync_with_stdio(false);cin.tie(0);cin>>n>>m;for(inti=1;i<=m;i++){intu,v;cin>>u>>v;g[u].push_back(v);du[v]++;// v 的入度+1}if(!topo()){cout<<-1<<endl;}else{for(inti=0;i<tp.size();i++){cout<<tp[i]<<" ";}cout<<endl;}return0;}
复杂度分析
  • 时间复杂度:O(n + m),每个点入队一次,每条边被遍历一次
  • 空间复杂度:O(n + m)
优缺点
优点缺点
直观易懂,实现简单需要记录入度
适合求字典序最小的拓扑序(改用优先队列)需要额外空间存储队列
可以同时检测环只能处理有向图

二、DFS 算法(三色标记法)

算法核心

DFS 版本的核心是深度优先搜索 + 三色标记,利用递归栈来判断是否存在环。

颜色定义
  • 白色(0):未访问
  • 灰色(-1):正在访问中(在递归栈里)
  • 黑色(1):已访问完毕
算法流程
  1. 对每个未访问的节点执行 DFS
  2. DFS 过程中:
    • 将当前节点标记为灰色(正在访问)
    • 遍历所有邻接点:
      • 如果邻接点是灰色 → 说明有环(遇到了祖先节点)
      • 如果邻接点是白色 → 递归访问
    • 访问完毕后,将当前节点标记为黑色,并加入拓扑序列
  3. 最后将拓扑序列反转(因为 DFS 是后序记录)
图解示例
图:1→2, 3→2, 4→2, 2→5, 6→5, 4→6 从1开始: 1(灰色) → 2(灰色) → 5(灰色) → 5(黑色) → 2(黑色) → 1(黑色) 从3开始: 3(灰色) → 2(已黑色,跳过) → 3(黑色) 从4开始: 4(灰色) → 2(已黑色) → 6(灰色) → 5(已黑色) → 6(黑色) → 4(黑色) 后序记录:[5, 2, 1, 3, 6, 4] 反转后:[4, 6, 3, 1, 2, 5] ✅
完整代码
#include<bits/stdc++.h>usingnamespacestd;constintN=100005;vector<int>g[N],tp;intvis[N];// 0=未访问, -1=访问中, 1=已访问intn,m;booldfs(intu){vis[u]=-1;// 标记为正在访问for(intv:g[u]){if(vis[v]==-1){returnfalse;// 发现环!}elseif(!vis[v]){if(!dfs(v)){returnfalse;}}}vis[u]=1;// 标记为已访问tp.push_back(u);// 后序记录returntrue;}booltopo(){memset(vis,0,sizeof(vis));for(inti=1;i<=n;i++){if(!vis[i]){if(!dfs(i)){returnfalse;}}}reverse(tp.begin(),tp.end());// 反转得到拓扑序returntrue;}intmain(){ios::sync_with_stdio(false);cin.tie(0);cin>>n>>m;for(inti=1;i<=m;i++){intu,v;cin>>u>>v;g[u].push_back(v);}if(!topo()){cout<<-1<<endl;}else{for(inti=0;i<tp.size();i++){cout<<tp[i]<<" ";}cout<<endl;}return0;}
为什么 DFS 要反转?

因为 DFS 是后序记录:先访问所有子节点,再记录当前节点。这导致记录的序列是"从叶子到根"的顺序,需要反转才能得到"从根到叶子"的拓扑序。

后序记录:[叶子, ..., 根] 反转后: [根, ..., 叶子] ← 拓扑序
复杂度分析
  • 时间复杂度:O(n + m),每个点访问一次,每条边遍历一次
  • 空间复杂度:O(n + m)
优缺点
优点缺点
不需要额外记录入度递归可能栈溢出(n大时需改非递归)
代码简洁需要理解三色标记
天然检测环反转操作需要注意

三、两种算法对比

对比维度Kahn 算法 (BFS)DFS 算法
核心思想维护入度为0的节点集合三色标记 + 递归回溯
数据结构队列(或优先队列)递归栈
是否需要反转❌ 不需要✅ 需要
环检测拓扑序列长度 < n遇到灰色节点
字典序最小✅ 改用优先队列即可❌ 不易实现
空间占用O(n) 额外空间O(n) 递归栈
适用场景直观,易理解递归思维,代码简洁

四、常见应用场景

  1. 课程安排:判断能否修完所有课程
  2. 编译依赖:确定文件编译顺序
  3. 任务调度:确定任务执行顺序
  4. 解决依赖关系:如包管理器安装顺序

五、优化技巧

1. 字典序最小的拓扑序

使用优先队列代替普通队列:

priority_queue<int,vector<int>,greater<int>>q;// 小根堆
2. 大数据的 DFS 防爆栈

使用非递归 DFS或增大栈空间,或改用 Kahn 算法。

3. 多组数据

每次重置数组和邻接表即可。


希望这篇博客对大家有所帮助!如有错误或建议,欢迎留言指正!📝完结撒花!!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 23:29:20

无锡芯健细胞:免疫细胞存储适配人群全解析

无锡芯健细胞&#xff1a;免疫细胞存储适配人群全解析免疫细胞存储的核心价值并非大众认知的「未来保险」&#xff0c;真正决定适配性的是健康管理底层需求。——这是无锡芯健细胞深耕细胞生物技术5年&#xff0c;基于数千例存储案例与健康管理实战经验得出的结论。一、拆解本质…

作者头像 李华
网站建设 2026/8/23 23:27:33

雨晨 Windows 11 IoT 企业版 26H1 轻装 28120.2760

文件: 雨晨 Windows 11 IoT 企业版 26H1 轻装 28120.2760.wim 大小: 3953936566 字节 修改时间: 2026年8月22日, 星期六, 08:11:00 MD5: C098DFA89EA18F9759B8BB40543DD83D SHA1: DDD8389C29717AD458B43C9E71ECFCD685D3CEAC CRC32: 6843B70Ehttps://1811716252.share.123pan.cn…

作者头像 李华
网站建设 2026/8/23 23:26:38

工信部三级智能制造评审通关背后:一天,一个项目组,一家灯饰厂

中山市明某智能照明迎来工信部智能制造三级现场评审。精工智能项目团队全天驻场&#xff0c;用半天时间跑完从车间设备联调到跨部门流程拉通的全流程彩排&#xff0c;下午完成迎审布置与现场答辩支撑。本文记录了一次评审攻坚的真实过程——智能制造能力成熟度评估&#xff0c;…

作者头像 李华
网站建设 2026/8/23 23:10:40

德系车维修质保体系的技术支撑分析:从配件追溯到施工标准化

汽车维修质保承诺的技术可行性&#xff0c;取决于三个核心支撑&#xff1a;配件品质的可追溯性、诊断流程的标准化程度、以及施工规范的执行一致性。本文从这三个维度分析德系车维修质保体系的技术架构&#xff0c;并以独立专修店的技术迁移实践为案例。一、质保体系的三层技术…

作者头像 李华
网站建设 2026/8/23 23:05:17

python的运筹学工业场景模拟第九十二篇:金属型材下料,多种型材原料,多规格零件,整数规划,最小原料消耗,统计边角料。

型材下料“省料神器”&#xff1a;用整数规划把边角料变成利润“某钢结构车间每月切 500 吨 H 型钢&#xff0c;要出 2000 多种零件。老下料工凭经验‘先长后短’&#xff0c;材料利用率只有 82%&#xff0c;每月剩 90 吨边角料&#xff0c;当废铁卖亏 45 万。后来我用 Python …

作者头像 李华