当前位置: 首页 > news >正文

拓扑排序(Topological Sort)

什么是拓扑排序

拓扑排序是对**有向无环图(DAG, Directed Acyclic Graph)**的节点进行线性排序,使得对于图中的每一条有向边u → v,节点u在排序中都位于节点v之前。

核心特性

  • 仅适用于 DAG:图必须无环,否则无法进行拓扑排序
  • 结果不唯一:一个 DAG 可能有多个合法的拓扑排序
  • 应用场景:任务调度、课程选修计划、编译依赖、数据流处理等

两种经典算法

1. Kahn 算法(BFS 思路)

核心思想:不断移除入度为 0 的节点

1. 计算所有节点的入度 2. 将所有入度为 0 的节点加入队列 3. 依次取出节点,将其邻接节点入度减 1 4. 若邻接节点入度变为 0,加入队列 5. 重复直到队列为空 6. 检查是否所有节点都被处理(判断是否有环)

2. DFS 算法

核心思想:利用 DFS 的完成时间逆序

1. 对图进行 DFS 遍历 2. 当某个节点的所有邻接节点都访问完成后,将该节点加入结果 3. 最后将结果逆序,即为拓扑排序

Java 实现(Kahn 算法)

importjava.util.*;publicclassTopologicalSort{// 邻接表表示图privateList<List<Integer>>graph;privateintn;publicTopologicalSort(intn){this.n=n;this.graph=newArrayList<>();for(inti=0;i<n;i++){graph.add(newArrayList<>());}}// 添加有向边 u -> vpublicvoidaddEdge(intu,intv){graph.get(u).add(v);}// Kahn 算法(BFS)publicList<Integer>kahnSort(){// 1. 计算入度int[]inDegree=newint[n];for(intu=0;u<n;u++){for(intv:graph.get(u)){inDegree[v]++;}}// 2. 入度为0的节点入队Queue<Integer>queue=newLinkedList<>();for(inti=0;i<n;i++){if(inDegree[i]==0){queue.offer(i);}}// 3. BFS 处理List<Integer>result=newArrayList<>();while(!queue.isEmpty()){intu=queue.poll();result.add(u);for(intv:graph.get(u)){inDegree[v]--;if(inDegree[v]==0){queue.offer(v);}}}// 4. 检查是否有环if(result.size()!=n){thrownewRuntimeException("图中存在环,无法进行拓扑排序!");}returnresult;}// DFS 算法publicList<Integer>dfsSort(){boolean[]visited=newboolean[n];boolean[]onPath=newboolean[n];// 用于检测环Deque<Integer>stack=newArrayDeque<>();// 用栈存储结果for(inti=0;i<n;i++){if(!visited[i]){dfs(i,visited,onPath,stack);}}List<Integer>result=newArrayList<>();while(!stack.isEmpty()){result.add(stack.pop());}returnresult;}privatevoiddfs(intu,boolean[]visited,boolean[]onPath,Deque<Integer>stack){visited[u]=true;onPath[u]=true;for(intv:graph.get(u)){if(onPath[v]){thrownewRuntimeException("图中存在环!");}if(!visited[v]){dfs(v,visited,onPath,stack);}}onPath[u]=false;stack.push(u);// 后序遍历位置加入结果}// 测试publicstaticvoidmain(String[]args){// 示例:课程选修 0->2, 1->2, 2->3, 2->4// 表示:课程0和1是课程2的先修课,课程2是课程3和4的先修课TopologicalSortts=newTopologicalSort(5);ts.addEdge(0,2);ts.addEdge(1,2);ts.addEdge(2,3);ts.addEdge(2,4);System.out.println("Kahn算法结果: "+ts.kahnSort());System.out.println("DFS算法结果: "+ts.dfsSort());// 输出可能是: [0, 1, 2, 3, 4] 或 [1, 0, 2, 4, 3] 等合法排序}}

图解示例

0 1 入度表: 0:0, 1:0, 2:2, 3:1, 4:1 \ / ↘ ↘ 2 → 3 ↓ 4 Kahn算法执行过程: 1. 初始入度为0: [0, 1],加入结果 2. 移除0,2的入度变为1;移除1,2的入度变为0,加入队列 3. 移除2,3和4的入度变为0,加入队列 4. 依次移除3、4 拓扑排序结果: [0, 1, 2, 3, 4] 或 [1, 0, 2, 4, 3] 等

复杂度分析

算法时间复杂度空间复杂度
Kahn (BFS)O(V + E)O(V)
DFSO(V + E)O(V)

其中 V 是顶点数,E 是边数。


实际应用

  1. Maven/Gradle 依赖解析:确定 jar 包的加载顺序
  2. Makefile 编译顺序:确定源文件的编译先后
  3. 数据库迁移脚本:按依赖关系执行 DDL
  4. Spark 任务调度:确定 RDD 转换的执行顺序
http://www.cnnetsun.cn/news/1351873.html

相关文章:

  • 提示系统SQL优化从慢到快:架构师用提示工程实现查询响应速度提升10倍
  • Spring Boot 配置文件优先级机制
  • C++中的策略模式实战
  • 记一个BUG:Trae里MongoDB和MySQL MCP不能共存
  • vue根据数字显示对应的文字状态
  • 基于STM32F4的CANopen快速SDO通信(超级详细)
  • 【UART】Verilog实现UART接收和发送模块
  • 实战案例:使用混合推理打造高性能AI原生应用
  • VMware Horizon 8安装部署(一)AD域的安装
  • 主板STM32,GD32等MCU电路设计思维-状态提示
  • Qt 中多媒体模块的使用
  • 探索音乐创新:PedalinoMini™ —— 蓝牙WiFi MIDI控制器
  • 爬虫 APP 逆向 ---> 粉笔考研
  • AtomPePacker 开源项目教程
  • 北航论文格式零失误:BUAAthesis模板中图表、公式与代码块的规范使用
  • 从明文暴露到安全存储:Keyring彻底解决Python密码管理痛点
  • PDF4QT命令行工具详解:自动化处理PDF文档的实用技巧
  • java基于微信小程序的陕西省红色旅游管理系统_dkv6632x
  • leetcode153.寻找旋转排序数组中的最小值
  • MySQL数据库初体验
  • 【即插即用完整代码】CVPR 2026新方法归一化空间与通道注意力,无额外参数,轻量且高效,超越CBAM,快速涨点,发表论文!
  • BetterNCM 插件导致网易云音乐启动失败问题分析
  • Camera:实时监控与数据交互的智能设备服务
  • 事件日志清理与痕迹擦除:wmiexec-Pro的eventlog模块深度应用
  • 告别厂商限制:Tuya TH05Z温湿度传感器接入Zigbee2MQTT完全指南
  • 复购率不理想如何用产品线组合提升长期价值
  • SimpleMem快速上手指南:5分钟搭建LLM智能体记忆系统
  • 基于LangChain的RAG与Agent智能体开发 - 使用LangChain调用大语言模型
  • MiniChain与Hugging Face Datasets:实现高效文档嵌入与相似度搜索的完整指南
  • Deepagents数据可视化:展示AI代理工作成果的终极指南