目录

一、什么是拓扑排序?

二、拓扑排序的算法实现

1 BFS算法实现

(1)算法思路

(2) 代码实现(Java)

2 DFS算法实现

(1)算法思路

(2) 代码实现(Java)

三、实战用例:具有优先级的任务调度系统

1 问题描述

2 解决方案

3 Java 实现


一、什么是拓扑排序?

拓扑排序(Topological Sorting)是图论中的一种排序方式,主要用于有向无环图(DAG, Directed Acyclic Graph)

在拓扑排序中,如果图中的边 u → v 表示任务 u 必须在任务 v 之前完成,那么拓扑排序就返回一个任务执行顺序,使得所有的先决任务都排在其后续任务之前。

如果图中存在环(即不是DAG),就不存在合法的拓扑排序。

二、拓扑排序的算法实现

我们以无权图(不含权重)为例,使用 Java 实现拓扑排序的两种主流算法:

1 BFS算法实现

Kahn算法是一种经典的基于BFS(广度优先搜索)的拓扑排序算法

(1)算法思路
  1. 统计每个节点的入度(被依赖的次数)。

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

  3. 从队列中取出一个节点,将其加入拓扑排序结果。

  4. 遍历该节点的所有邻接节点,将邻接节点的入度减 1;若入度变为 0,则加入队列。

  5. 重复步骤 3 和 4,直到队列为空。

  6. 如果结果中的节点数不等于图的节点总数,则说明图中存在环。

(2) 代码实现(Java)
import java.util.*;
​
public class TopSort {public static List<String> topSortKahn(Map<String, List<String>> graph) {// 1. 计算每个节点的入度Map<String, Integer> indegree = new HashMap<>();for (String u : graph.keySet()) {// 确保所有节点都在 indegree 中indegree.putIfAbsent(u, 0);for (String v : graph.get(u)) {indegree.put(v, indegree.getOrDefault(v, 0) + 1);}}
​// 2. 所有入度为 0 的节点入队Queue<String> queue = new LinkedList<>();for (Map.Entry<String, Integer> entry : indegree.entrySet()) {if (entry.getValue() == 0) {queue.offer(entry.getKey());}}
​// 3. 进行拓扑排序List<String> result = new ArrayList<>();while (!queue.isEmpty()) {String node = queue.poll();result.add(node);
​List<String> neighbors = graph.getOrDefault(node, new ArrayList<>());for (String neighbor : neighbors) {indegree.put(neighbor, indegree.get(neighbor) - 1);if (indegree.get(neighbor) == 0) {queue.offer(neighbor);}}}
​// 4. 如果排序结果数量 < 节点总数,说明有环if (result.size() != indegree.size()) {return null;}
​return result;}// 测试用例public static void main(String[] args) {Map<String, List<String>> graph1 = new HashMap<>();graph1.put("A", Arrays.asList("C"));graph1.put("B", Arrays.asList("C", "D"));graph1.put("C", Arrays.asList("E"));graph1.put("D", Arrays.asList("F"));graph1.put("E", Arrays.asList("H", "F"));graph1.put("F", Arrays.asList("G"));graph1.put("G", Collections.emptyList());graph1.put("H", Collections.emptyList());
​System.out.println("拓扑排序结果: " + topSortKahn(graph1));
​// 带有环的图Map<String, List<String>> graph2 = new HashMap<>();graph2.put("A", Arrays.asList("C"));graph2.put("B", Arrays.asList("C", "D"));graph2.put("C", Arrays.asList("E"));graph2.put("D", Arrays.asList("F"));graph2.put("E", Arrays.asList("C")); // 构成了环graph2.put("F", Arrays.asList("G"));graph2.put("G", Collections.emptyList());
​System.out.println("拓扑排序结果(存在环): " + topSortKahn(graph2));}
}

测试结果:

拓扑排序结果: [A, B, C, D, E, H, F, G]
拓扑排序结果(存在环): null

2 DFS算法实现

(1)算法思路
  1. 使用 DFS 遍历图。

  2. 遍历过程中,将当前节点标记为“正在访问”状态。

  3. 递归访问邻接节点,如果发现邻接节点已经在访问中,说明存在环。

  4. 所有邻接节点访问完后,将当前节点加入结果栈,并标记为“访问完成”。

  5. 最终将栈反转,得到拓扑排序结果。

(2) 代码实现(Java)
import java.util.*;
​
public class TopSort {
​// 枚举节点访问状态enum State { UNVISITED, VISITING, VISITED }
​public static List<String> topSortDfs(Map<String, List<String>> graph) {Map<String, State> stateMap = new HashMap<>();List<String> result = new ArrayList<>();boolean[] hasCycle = new boolean[1]; // 记录是否存在环
​// 初始化所有节点为 UNVISITEDfor (String node : graph.keySet()) {stateMap.put(node, State.UNVISITED);for (String neighbor : graph.get(node)) {stateMap.putIfAbsent(neighbor, State.UNVISITED);}}
​// 对每个节点进行 DFSfor (String node : stateMap.keySet()) {if (stateMap.get(node) == State.UNVISITED) {dfs(node, graph, stateMap, result, hasCycle);if (hasCycle[0]) {return null; // 有环}}}
​// 逆序输出拓扑排序结果Collections.reverse(result);return result;}
​private static void dfs(String node, Map<String, List<String>> graph, Map<String, State> stateMap, List<String> result, boolean[] hasCycle) {stateMap.put(node, State.VISITING);
​for (String neighbor : graph.getOrDefault(node, Collections.emptyList())) {State neighborState = stateMap.get(neighbor);if (neighborState == State.UNVISITED) {dfs(neighbor, graph, stateMap, result, hasCycle);if (hasCycle[0]) return;} else if (neighborState == State.VISITING) {hasCycle[0] = true; // 检测到环return;}}
​stateMap.put(node, State.VISITED);result.add(node); // 后序加入}
​
​// 测试用例public static void main(String[] args) {Map<String, List<String>> graph1 = new HashMap<>();graph1.put("A", Arrays.asList("C"));graph1.put("B", Arrays.asList("C", "D"));graph1.put("C", Arrays.asList("E"));graph1.put("D", Arrays.asList("F"));graph1.put("E", Arrays.asList("H", "F"));graph1.put("F", Arrays.asList("G"));graph1.put("G", Collections.emptyList());graph1.put("H", Collections.emptyList());
​System.out.println("拓扑排序结果: " + topSortDfs(graph1));
​// 带有环的图Map<String, List<String>> graph2 = new HashMap<>();graph2.put("A", Arrays.asList("C"));graph2.put("B", Arrays.asList("C", "D"));graph2.put("C", Arrays.asList("E"));graph2.put("D", Arrays.asList("F"));graph2.put("E", Arrays.asList("C")); // 构成了环graph2.put("F", Arrays.asList("G"));graph2.put("G", Collections.emptyList());
​System.out.println("拓扑排序结果(存在环): " + topSortDfs(graph2));}
}

测试结果:

拓扑排序结果: [B, D, A, C, E, F, G, H]
拓扑排序结果(存在环): null

三、实战用例:具有优先级的任务调度系统

1 问题描述

我们有一个调度系统,其每个任务可能依赖其他任务,并且每个任务有一个优先级值(值越大优先级越高)。系统要求先执行优先级高的任务,同时也要保证所有依赖任务已经执行。

2 解决方案

我们结合拓扑排序 + 优先级排序解决:

  1. 将任务依赖建图。

  2. 使用拓扑排序(Kahn 算法)。

  3. 使用优先级高的任务优先执行:将普通队列替换成优先级队列,先处理高优先级入度为 0 的任务。

3 Java 实现

import java.util.*;public class TaskSchedulerWithPriority {public static class Task {private int id;private int priority;private List<Task> edges = new ArrayList<>(); // 边列表private int inDegree = 0; // 入度public Task(int id, int priority) {this.id = id;this.priority = priority;}public int getId() {return id;}public int getPriority() {return priority;}public void setPriority(int priority) {this.priority = priority;}public List<Task> getEdges() {return edges;}public int getInDegree() {return inDegree;}public void setInDegree(int inDegree) {this.inDegree = inDegree;}@Overridepublic String toString() {return "Task{id=" + id + ", priority=" + priority + "}";}}/*** 按优先级和依赖顺序进行拓扑排序*/public static List<Task> scheduleTasks(List<Task> tasks) {// 大顶堆:优先处理优先级高的任务PriorityQueue<Task> queue = new PriorityQueue<>((a, b) -> b.getPriority() - a.getPriority());// 初始化:将所有入度为 0 的任务放入队列for (Task task : tasks) {if (task.getInDegree() == 0) {queue.offer(task);}}List<Task> result = new ArrayList<>();while (!queue.isEmpty()) {Task current = queue.poll();result.add(current);// 遍历后继节点for (Task neighbor : current.getEdges()) {neighbor.setInDegree(neighbor.getInDegree()-1);if (neighbor.getInDegree() == 0) {queue.offer(neighbor);}}}// 检查是否有环if (result.size() != tasks.size()) {throw new RuntimeException("存在循环依赖,无法完成调度");}return result;}public static void main(String[] args) {// 创建任务Task t0 = new Task(0, 10);Task t1 = new Task(1, 20);Task t2 = new Task(2, 10);Task t3 = new Task(3, 20);// 构建依赖关系 (邻接表 + 入度)t0.getEdges().add(t2);t1.getEdges().add(t2);t2.getEdges().add(t3);t2.setInDegree(2); // 来自 t0 和 t1t3.setInDegree(1); // 来自 t2List<Task> tasks = Arrays.asList(t0, t1, t2, t3);// 调度List<Task> schedule = scheduleTasks(tasks);System.out.print("调度任务执行顺序: ");for (Task task : schedule) {System.out.print(task.id + " ");}}
}

测试结果

调度任务执行顺序: [1, 0, 2, 3]

表示优先级高的任务 1 和 0 会先执行,然后是它们的依赖任务 2 和最终任务 3。

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

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

相关文章

GoBy 工具联动 | GoBy AWVS 自动化漏扫工作流

GoBy 系统笔记导航 &#x1f680;&#xff1a;[网安工具] Web 漏洞扫描工具 —— GoBy 使用手册 AWVS 系统笔记导航 &#x1f680;&#xff1a;[网安工具] Web 漏洞扫描工具 —— AWVS 使用手册 0x01&#xff1a;GoBy AWVS —— 联动扫描简介 AWVS 是一款由 Acunetix 公司开…

《汇编语言:基于X86处理器》第13章 高级语言接口(1)

与C、c&#xff0c;Java等高级语言相比&#xff0c;汇编开发的效率偏低和维护成本偏高。大型的项目已经很少用汇编语言了&#xff0c;但并不是说汇编语言就完全没有用处了&#xff0c;在某些特定的领域&#xff0c;汇编语言还是很有用处的&#xff0c;比如配置硬件驱动器&#…

JVM基础【Java】

JVM基础 JVM&#xff1a;Java Virtual Machine(Java虚拟机&#xff09; 1.Java文件的执行流程 首先认识Java文件的运行规则对字节码文件进行解释成机器码&#xff0c;让计算机执行内存管理 自动为对象、方法等分配内存自动垃圾回收机制&#xff0c;回收不再使用的对象 即时编译…

ISL9V3040D3ST-F085C一款安森美 ON生产的汽车点火IGBT模块,绝缘栅双极型晶体管ISL9V3040D3ST汽车点火电路中的线圈驱动器

ISL9V3040D3ST-F085C 是一款 安森美 &#xff08;ON&#xff09;生产的汽车点火 IGBT模块&#xff08;绝缘栅双极型晶体管&#xff09;&#xff0c;主要用于汽车点火电路中的线圈驱动器&#xff0c;具有内部二极管电压箝位功能&#xff0c;可减少外部组件需求。‌ 核心用途 该…

用Python实现Excel转PDF并去除Spire.XLS水印

最近业务需要&#xff0c;成功用Python原生代码实现了原本需要付费的Spire.XLS库的Excel转PDF功能&#xff0c;并彻底去除了转换后PDF中的评估水印"Evaluation Warning: The document was created with Spire.XLS for Python"。该解决方案完全开源免费&#xff0c;不…

论文学习22:UNETR: Transformers for 3D Medical Image Segmentation

代码来源 unetr 模块作用 具有收缩和扩展路径的全卷积神经网络 (FCNN) 在大多数医学图像分割应用中表现出色&#xff0c;但卷积层的局部性限制了其学习长距离空间依赖性的能力。受 Transformer 在自然语言处理 (NLP) 领域近期在长距离序列学习方面取得的成功的启发&#xff…

Jmeter使用第一节-认识面板(Mac版)

常用的基础元件&#xff08;10个&#xff09;1、测试计划&#xff1a;总体项目容器&#xff0c;其他元件需要建立在这个目录下面2、线程组&#xff1a;可以设置线程数、循环次数等参数来模拟用户行为。一个用户可用于接口测试&#xff0c;多个用户则可用于性能压测。“线程数”…

微软披露Exchange Server漏洞:攻击者可静默获取混合部署环境云访问权限

微软近日发布安全公告&#xff0c;披露一个影响本地版Exchange Server的高危漏洞&#xff08;编号CVE-2025-53786&#xff0c;CVSS评分为8.0&#xff09;。该漏洞在特定条件下可能允许攻击者提升权限&#xff0c;Outsider Security公司的Dirk-jan Mollema因报告此漏洞获得致谢。…

大模型中的反向传播是什么

反向传播&#xff08;Backpropagation&#xff09;是大模型&#xff08;如GPT、BERT等&#xff09;训练过程中的核心算法&#xff0c;用于高效计算损失函数对神经网络中所有参数的梯度。这些梯度随后被用于优化器&#xff08;如Adam&#xff09;更新参数&#xff0c;使模型逐渐…

数集相等定义凸显解析几何几百年重大错误:将无穷多各异点集误为同一集

数集相等定义凸显解析几何几百年重大错误&#xff1a;将无穷多各异点集误为同一集 黄小宁 本文据中学生就应熟悉的数集相等概念推翻了直线公理和平面公理表明“举世公认”不能是检验真理的唯一标准。“真理往往在少数人手里”。 请看图片举世公认&#xff1a;因数学是严密精确的…

container_of函数使用

用于根据结构体成员的地址反推整个结构体地址的宏定义。其核心作用是通过成员变量地址定位到其所属的结构体实例。struct panel_tm145{struct drm_panel base;}static inline struct panel_tm145 * to_panel_tm145(struct drm_panel *panel){return container_of(panel, struct…

【MySQL基础篇】:MySQL索引——提升数据库查询性能的关键

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;MySQL篇–CSDN博客 文章目录索引一.MySQL与存储二.索引的理解1.Page页模式理解单个Page理解…

TD-IDF的一些应用

TF-IDF&#xff08;词频 - 逆文档频率&#xff09;作为经典的文本特征提取算法&#xff0c;在自然语言处理&#xff08;NLP&#xff09;领域应用广泛。它能将文本转化为可量化的数值特征&#xff0c;为后续的数据分析和建模提供基础。本文结合实际场景&#xff0c;介绍如何用 P…

Redis 缓存问题详解及解决方案

一、缓存击穿 (Cache Breakdown) 原理&#xff1a; 某个热点 Key 突然过期&#xff0c;同时大量并发请求该 Key&#xff0c;导致请求直接穿透缓存击穿到数据库。 解决方案&#xff1a; 互斥锁 (Mutex Lock) 当缓存失效时&#xff0c;仅允许一个线程重建缓存&#xff0c;其他线程…

一周一个数据结构 第一周 --- 顺序表(下)

文章目录一、ArrayList的构造二、ArrayList常见操作三、ArrayList的遍历四、ArrayList练习1.【小练习】2.杨辉三角3.简单的洗牌算法五、ArrayList小结在上一章节中&#xff0c;我们通过代码示例以及画图的方式详细了解了顺序表&#xff0c;并模拟实现了它。那么&#xff0c;是不…

OpenCV的关于图片的一些运用

一、读取图片通过cv2库中的imread&#xff08;&#xff09;方法读取图片代码&#xff1a;import cv2 a cv2.imread(1.png) cv2.imshow(tu,a) b cv2.waitKey(4000) # 图片执行时间 cv2.destroyAllWindows() # 关闭所有端口 print("图像形状(shape):",a.shape) print…

【数据结构——并查集】

引入 并查集&#xff08;Disjoint Set Union&#xff0c;DSU&#xff09;是一种用于管理元素分组的数据结构。 合并&#xff08;Union&#xff09;&#xff1a;将两个不相交的集合合并为一个集合。 查找&#xff08;Find&#xff09;&#xff1a;确定某个元素属于哪个集合&…

在 Vue 中使用 ReconnectingWebSocket实现即时通讯聊天客服功能

在 Vue 中使用 ReconnectingWebSocketReconnectingWebSocket 是一个自动重连的 WebSocket 实现&#xff0c;非常适合在 Vue 项目中使用。下面是如何在 Vue 中集成和使用它的方法&#xff1a;搜索 "程序员老狼"安装 ReconnectingWebSocket首先&#xff0c;你需要安装…

智能体革命:网络安全人的角色重塑与突围指南

AI赋能千行百业的趋势不可逆转&#xff0c;当AI学会渗透测试&#xff0c;安全工程师的出路在哪里&#xff1f; 2025年8月7日&#xff0c;OpenAI正式发布GPT-5的消息刷屏科技圈。这个达到博士生水平的“统一”人工智能模型&#xff0c;将AI幻觉率降低60%&#xff0c;成本下降45%…

用于水T1值和脂肪分数量化的上半身自由呼吸磁共振指纹成像|文献速递-医学影像算法文献分享

Title题目Upper-body free-breathing Magnetic Resonance Fingerprinting applied tothe quantification of water T1 and fat fraction用于水T1值和脂肪分数量化的上半身自由呼吸磁共振指纹成像 01文献速递介绍磁共振指纹成像&#xff08;MRF&#xff09;是十年前推出的一种高…