拓扑排序(Topological Sorting)是一种针对有向无环图(DAG)的线性排序算法,它将图中的顶点按照一定规则排列,使得对于图中的任意一条有向边 u→v,顶点 u 都排在顶点 v 之前。拓扑排序在任务调度、课程安排、编译依赖等场景中有着广泛应用。


拓扑排序的基本概念与适用场景

核心定义

  • 有向无环图(DAG):顶点间的边有方向,且不存在环路的图。拓扑排序仅适用于 DAG,若图中存在环,则不存在拓扑排序。
  • 拓扑序:对于 DAG 中的顶点,存在一个线性序列 v₁, v₂, ..., vₙ,使得所有有向边 vᵢ→vⱼ 均满足 i < j。一个 DAG 可能存在多个拓扑序。

应用场景

  • 任务调度:若任务 B 依赖任务 A,则 A 必须在 B 之前执行,拓扑排序可确定任务执行顺序。
  • 课程安排:大学课程存在先修关系(如 “数据结构” 需先修 “C 语言”),拓扑排序可生成合理的选课顺序。
  • 编译依赖:编译器处理源文件时,需按依赖关系(如头文件包含)确定编译顺序。

DAG 与拓扑序示例

拓扑排序的核心算法

Kahn算法(基于入度与队列)

核心思想:通过维护节点的入度(指向该节点的边数),逐步选出入度为0的节点(无前置依赖),并减少其邻接节点的入度,直至所有节点被处理或检测到环。

算法步骤

1. 计算入度:遍历图,记录每个节点的入度。

2. 初始化队列:将所有入度为0的节点加入队列。

3. 生成拓扑序

- 出队一个节点,加入拓扑序。

- 对该节点的所有邻接节点,入度减1;若入度变为0,入队。

4. 检测环:若拓扑序长度小于节点总数,则图中存在环,无拓扑序。

基于DFS的拓扑排序

核心思想:通过深度优先搜索,在回溯时将节点加入拓扑序(逆后序遍历),若搜索中遇到回边(指向已访问但未回溯的节点),则存在环。

算法步骤

1. 标记访问状态:用三个状态标记节点:未访问(0)、访问中(1)、已访问(2)。

2. 递归DFS

- 若节点状态为访问中,检测到环。

- 若节点未访问,标记为访问中,递归遍历所有邻接节点。

- 回溯时,标记节点为已访问,加入拓扑序。

3. 逆序输出:拓扑序为逆后序遍历结果。

LeetCode例题实战

例题1:207. 课程表(中等)

题目描述:你这个学期必须选修 `numCourses` 门课程,记为 `0` 到 `numCourses - 1`。给你一个数组 `prerequisites`,其中 `prerequisites[i] = [ai, bi]`,表示在选修课程 `ai` 之前必须先选修 `bi`。判断是否可能完成所有课程的学习。

示例

输入:numCourses = 2, prerequisites = [[1,0]]

输出:true(先学 0,再学 1)

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]

输出:false(存在环 0→1→0)

解题思路(Kahn算法)

1. 构建图:用邻接表表示课程依赖关系,数组 `inDegree` 记录每个课程的入度。

2. 初始化队列:将入度为0的课程入队。

3. 拓扑排序:处理队列中的课程,减少依赖它的课程的入度,统计已处理课程数。

4. 判断结果:若已处理课程数等于 `numCourses`,则无环,可完成;否则有环,不可完成。

代码实现
import java.util.*;class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {// 1. 构建邻接表和入度数组List<List<Integer>> adj = new ArrayList<>();int[] inDegree = new int[numCourses];for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int course = p[0];int pre = p[1];adj.get(pre).add(course); // pre → courseinDegree[course]++;}// 2. 初始化队列(入度为0的课程)Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 3. 拓扑排序int count = 0; // 已处理的课程数while (!queue.isEmpty()) {int curr = queue.poll();count++;// 处理依赖curr的课程for (int next : adj.get(curr)) {inDegree[next]--;if (inDegree[next] == 0) {queue.offer(next);}}}// 4. 判断是否所有课程都被处理return count == numCourses;}}
复杂度分析
  • 时间复杂度:O (V + E),V 为节点数(课程数),E 为边数(依赖关系数)。
  • 空间复杂度:O (V + E),邻接表存储图,队列存储节点。

例题 2:210. 课程表 II(中等)

题目描述:同上题,但需返回一个可行的拓扑序;若无法完成所有课程,返回空数组。

示例

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]

输出:[0,1,2,3] 或 [0,2,1,3] 等

解题思路

在 Kahn 算法中,将出队的节点依次加入拓扑序列表,最后若列表长度等于课程数则返回,否则返回空数组。

代码实现
import java.util.*;class Solution {public int[] findOrder(int numCourses, int[][] prerequisites) {// 1. 构建邻接表和入度数组List<List<Integer>> adj = new ArrayList<>();int[] inDegree = new int[numCourses];for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int course = p[0];int pre = p[1];adj.get(pre).add(course);inDegree[course]++;}// 2. 初始化队列Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 3. 拓扑排序并记录结果List<Integer> topoOrder = new ArrayList<>();while (!queue.isEmpty()) {int curr = queue.poll();topoOrder.add(curr);for (int next : adj.get(curr)) {inDegree[next]--;if (inDegree[next] == 0) {queue.offer(next);}}}// 4. 检查是否有环if (topoOrder.size() != numCourses) {return new int[0];}// 转换为数组int[] result = new int[numCourses];for (int i = 0; i < numCourses; i++) {result[i] = topoOrder.get(i);}return result;}}
复杂度分析
  • 时间复杂度:O (V + E),同 Kahn 算法。
  • 空间复杂度:O (V + E),额外存储拓扑序列表。

考研 408 例题解析

例题 1:概念辨析题(选择题)

题目:下列关于拓扑排序的叙述中,错误的是( )。

A. 拓扑排序仅适用于有向无环图(DAG)

B. 一个 DAG 的拓扑序是唯一的

C. Kahn 算法通过维护入度为 0 的节点生成拓扑序

D. 基于 DFS 的拓扑排序需检测回边以判断是否有环

答案:B

解析

  • A 正确:有环图不存在拓扑序。
  • B 错误:一个 DAG 可能有多个拓扑序(如示例中的 A→B→C 和 A→D→B)。
  • C 正确:Kahn 算法的核心是入度管理。
  • D 正确:DFS 中遇到回边(访问中状态的节点)说明有环。

例题 2:算法设计题(408 高频考点)

题目:已知一个有向图用邻接表表示,设计一个算法判断该图是否为 DAG,若为 DAG 则输出其拓扑序,否则输出 “有环”。要求使用 Kahn 算法,并分析时间复杂度。

解题思路
  1. 数据结构:邻接表 adj 存储图,数组 inDegree 存储入度。
  2. 算法步骤
    • 计算所有节点的入度。
    • 入度为 0 的节点入队,初始化拓扑序列表。
    • 处理队列,更新入度和拓扑序,统计处理节点数。
    • 若处理节点数等于总节点数,输出拓扑序;否则输出 “有环”。
代码实现
import java.util.*;public class TopologicalSort {// 判断是否为DAG并返回拓扑序,否则返回nullpublic List<Integer> isDAGAndTopoOrder(List<List<Integer>> adj, int n) {int[] inDegree = new int[n];// 计算入度for (int u = 0; u < n; u++) {for (int v : adj.get(u)) {inDegree[v]++;}}Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < n; i++) {if (inDegree[i] == 0) {queue.offer(i);}}List<Integer> topoOrder = new ArrayList<>();while (!queue.isEmpty()) {int u = queue.poll();topoOrder.add(u);for (int v : adj.get(u)) {inDegree[v]--;if (inDegree[v] == 0) {queue.offer(v);}}}return topoOrder.size() == n ? topoOrder : null;}public static void main(String[] args) {// 示例:DAGint n = 4;List<List<Integer>> adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}adj.get(0).add(1);adj.get(0).add(2);adj.get(1).add(3);adj.get(2).add(3);TopologicalSort ts = new TopologicalSort();List<Integer> result = ts.isDAGAndTopoOrder(adj, n);if (result != null) {System.out.println("拓扑序:" + result); // 输出:[0,1,2,3] 或类似} else {System.out.println("有环");}}}
复杂度分析
  • 时间复杂度:O (V + E),V 为节点数,E 为边数。计算入度需 O (E),队列操作和入度更新共 O (V + E)。
  • 空间复杂度:O (V + E),邻接表占 O (E),队列和拓扑序占 O (V)。

拓扑排序的扩展与应用

实际应用场景

  • 项目管理:微软 Project 等工具用拓扑排序安排任务依赖关系。
  • 编译系统:GCC 编译器处理源文件依赖,按拓扑序编译。
  • 任务调度:操作系统进程调度中,按资源依赖关系确定执行顺序。
  • 课程平台:MOOC 平台推荐先修课程,生成学习路径。

与其他图算法的关联

  • 强连通分量(SCC):有环图的 SCC 可收缩为单个节点,形成 DAG 后再拓扑排序。
  • 关键路径:在带权 DAG 中,拓扑序可高效计算最长路径(关键路径)。
  • 最短路径:DAG 的单源最短路径可通过拓扑序在 O (V + E) 时间内求解。

考研 408 备考要点

  • 核心考点:拓扑排序的适用条件、Kahn 算法步骤、环检测方法。
  • 重点掌握
  1. 邻接表的构建与入度计算。
  2. Kahn 算法中队列的作用及拓扑序生成逻辑。
  3. 拓扑排序与 DAG 的等价关系(存在拓扑序 ⇨ 是 DAG)。
  • 常见错误
    • 忽略入度为 0 的节点初始队列可能为空(直接判定有环,但需确认节点数是否为 0)。
    • 处理邻接节点时漏减入度,导致算法错误。

总结

拓扑排序是处理有向无环图(DAG)的核心算法,其 Kahn 算法和 DFS 算法各有优势:Kahn 算法直观易实现,适合求拓扑序;DFS 算法更简洁,适合环检测。本文通过 LeetCode 例题(课程表系列)展示了算法的实际应用,通过考研 408 例题巩固了理论基础,结合 SVG 图示清晰呈现了算法流程。

掌握拓扑排序的关键在于:

  1. 理解 DAG 与拓扑序的一一对应关系。
  2. 熟练实现 Kahn 算法的入度管理和队列操作。
  3. 能将实际问题抽象为 DAG,并用拓扑排序解决依赖关系问题。

在考研备考中,需重点关注算法的时间复杂度分析和环检测逻辑,这不仅是考试重点,也是解决复杂图问题的基础。

希望本文能够帮助读者更深入地理解拓扑排序算法,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90583.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90583.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90583.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用Web3加密技术保障您的在线数据安全

在这个信息爆炸的数字化时代&#xff0c;保护个人和企业数据安全变得尤为重要。Web3技术以其去中心化和加密特性&#xff0c;为在线数据安全提供了新的解决方案。本文将探讨Web3技术如何通过加密技术保障您的在线数据安全&#xff0c;并介绍如何有效利用这些技术。 什么是Web3技…

Vue实现el-checkbox单选并回显选中

先说需求 我要在页面进行checkbox单选并回显 第一步先把基本的页面写好噢&#xff1a;vue代码&#xff1a;别忘了写change啊<el-form-item label"按钮颜色:" prop"menuColor"><el-checkbox-group v-model"buttonColor" change"bin…

动态规划--序列找优问题【1】

一、说明 动态规划似乎针对问题很多&#xff0c;五花八门&#xff0c;似乎每一个问题都有一套具体算法。其实不是的&#xff0c;动态规划只有两类&#xff1a;1&#xff09;针对图的路径问题 2&#xff09;针对一个序列的问题。本篇讲动态规划针对序列的算法范例。 二、动态规划…

独家|百度副总裁尚国斌即将离职,此前统筹百度地图;行业搜索及智能体业务总经理谢天转岗IDG

百度人事再变动。作者|文昌龙编辑|杨舟据「市象」了解&#xff0c;近期&#xff0c;百度副总裁尚国斌即将离职。公开资料显示&#xff0c;尚国斌2010年毕业于南开大学&#xff0c;2012年加入百度&#xff0c;先后在商业分析部、集团战略办、智能驾驶事业群工作。尚国斌同样也在…

Qt 网络编程进阶:HTTP 客户端实现

在 Qt 应用程序中&#xff0c;实现高性能、可靠的 HTTP 客户端是常见需求。Qt 提供了丰富的网络模块&#xff0c;包括 QNetworkAccessManager、QNetworkRequest 和 QNetworkReply 等类&#xff0c;用于简化 HTTP 通信。本文将深入探讨 Qt 网络编程中 HTTP 客户端的进阶实现&…

Python Requests-HTML库详解:从入门到实战

一、库简介 Requests-HTML是Python中集网络请求与HTML解析于一体的全能型库&#xff0c;由知名开发者Kenneth Reitz团队维护。它完美结合了Requests的易用性和Parsel的选择器功能&#xff0c;并内置JavaScript渲染引擎&#xff0c;特别适合现代动态网页抓取。最新版本&#xf…

基于springboot的小区车位租售管理系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

Kafka 如何优雅实现 Varint 和 ZigZag 编码

ByteUtils 是 Kafka 中一个非常基础且核心的工具类。从包名 common.utils 就可以看出&#xff0c;它被广泛用于 Kafka 的各个模块中。它的主要职责是提供一套高效、底层的静态方法&#xff0c;用于在字节缓冲区 (ByteBuffer)、字节数组 (byte[]) 以及输入/输出流 (InputStream/…

局域网 IP地址

很多童鞋搞不清楚局域网ip是什么? 什么是局域网 IP 地址? 局域网 IP 地址,也称为 私有 IP 地址(Private IP Address),是用于在局域网内部标识设备的地址。这些地址不能直接在互联网上被访问,通常由路由器自动分配,用于设备之间的内部通信。 局域网 IP 地址的分类 根…

k8s的service、deployment、探针详解

1.k8s组成图2.service和deployment的流量转发图# Deployment 定义容器端口 apiVersion: apps/v1 kind: Deployment metadata:name: myapp spec:template:spec:containers:- name: nginximage: nginxports:- containerPort: 80 # 容器监听 80name: http # 端口命名&…

【PostgreSQL教程】PostgreSQL中json类型与jsonb类型的区别

博主介绍:✌全网粉丝23W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…

牛客刷题记录01

除2&#xff01; 目录 除2&#xff01; 题目描述&#xff1a; ​编辑 题目解析&#xff1a; 代码实现&#xff1a; 数组中两个字符串的最小距离__牛客网 题目描述&#xff1a; 题目解析&#xff1a; 代码实现&#xff1a; 除2&#xff01; 题目描述&#xff1a; 给一个…

Docker Compose UI远程访问教程:结合贝锐花生壳实现内网穿透

对于很多刚接触Docker的用户来说&#xff0c;命令行操作总带着一丝“劝退感”。尤其是要在Windows上部署服务、开放端口、配置参数时&#xff0c;稍有不慎就容易出错。有没有办法像网页后台一样&#xff0c;用图形界面来管理Docker项目呢&#xff1f;答案是&#xff1a;有&…

HF83311_VB1/HF83311Q_VB1:高性能USB HiFi音频解码器固件技术解析

引言随着高品质音频体验需求的不断增长&#xff0c;音频解码器固件的性能和功能成为决定音频设备品质的关键因素。本文将介绍一款基于XMOS XU316技术的高性能USB HiFi音频解码器固件——HF83311_VB1/HF83311Q_VB1&#xff0c;这是一款专为USB HiFi音频应用设计的软件解决方案。…

[ComfyUI] -入门1-ComfyUI 是什么?比 Stable Diffusion WebUI 强在哪?

ComfyUI 是一个开源的、节点可视化界面,用于构建与执行 Stable Diffusion 图像生成流程。它把复杂的生成过程拆解为许多“节点”(如提示编码、采样器、控制网络等),用户通过连接节点,就能自由编排工作流 。这种设计适合开发者与进阶用户,更便于微调、多分支与复用流程。 …

[python][flask]flask接受get或者post参数

在 Flask 中&#xff0c;可以通过 request 对象来获取客户端通过 GET 或 POST 方法发送的参数。以下是如何在 Flask 中接收 GET 和 POST 参数的详细说明&#xff1a;1. 接收 GET 参数GET 请求的参数通常通过 URL 的查询字符串传递。例如&#xff0c;对于 URL http://example.co…

Creo 模块众多,企业如何按需灵活分配许可证资源?

在数字化设计与智能制造深入发展的当下&#xff0c;企业 CAD/CAE 工具的精细化管理越来越重要。Creo&#xff0c;作为 PTC 旗下一体化 3D CAD 平台&#xff0c;以其模块化、可扩展的产品架构&#xff0c;广泛应用于机械、装备、汽车、航空航天等行业。其丰富的模块库覆盖建模设…

【c++】提升用户体验:问答系统的交互优化实践——关于我用AI编写了一个聊天机器人……(12)

本期依旧使用豆包辅助完成代码。从功能到体验的转变上个版本已经实现了问答系统的核心功能&#xff1a;基于 TF-IDF 算法的问题匹配和回答。它能够读取训练数据&#xff0c;处理用户输入&#xff0c;并返回最相关的答案。但在用户体验方面还有很大提升空间。让我们看看改进版做…

Android UI 控件详解实践

一、UI 开发基础概念&#xff08;初学者必看&#xff09; 在学习具体控件前&#xff0c;先理解以下核心概念&#xff0c;能大幅降低后续学习难度&#xff1a; 1. View 与 ViewGroup 的关系 View&#xff1a;所有 UI 控件的基类&#xff08;如 Button、TextView&#xff09;&…

关于linux运维 出现高频的模块认知

一、Linux 基础核心&#xff08;必掌握&#xff09;核心工具&#xff1a;Shell 脚本、Systemd、用户权限管理、日志分析&#xff08;journalctl、rsyslog&#xff09;企业需求&#xff1a;中小型公司&#xff1a;需独立完成系统部署、故障排查&#xff0c;对脚本开发&#xff0…