拓扑排序:Kahn算法与DFS算法
引言
拓扑排序是有向无环图(DAG)中的一种线性排序,使得对于图中的每一条有向边 ( u \rightarrow v ),顶点 ( u ) 在排序中出现在顶点 ( v ) 之前。本文将详细介绍两种实现拓扑排序的算法:Kahn算法和基于深度优先搜索(DFS)的算法。
目录
- Kahn算法
- 基于DFS的算法
Kahn算法
定义
Kahn算法是一种基于入度的拓扑排序算法。该算法通过不断移除入度为0的顶点及其边来构建拓扑排序。
算法步骤
- 初始化:计算图中所有顶点的入度,并将所有入度为0的顶点添加到一个队列中。
- 构建排序:从队列中取出一个顶点,将其添加到拓扑排序的结果中,并移除该顶点及其所有出边。对于每个被移除的出边,如果目标顶点的入度减为0,则将该顶点添加到队列中。
- 检测环:重复步骤2,直到队列为空。如果排序结果中的顶点数量小于图中的顶点数量,则说明图中存在环,无法进行拓扑排序。
示例
假设我们有一个有向无环图,顶点集合为 ({A, B, C, D, E, F}),边集合为 ({(A, C), (B, C), (B, D), (C, E), (D, F), (E, F)})。
Kahn算法实现
下面是用Java实现Kahn算法的代码示例:
import java.util.*;public class KahnAlgorithm {private int vertices; // 顶点数量private List<Integer>[] adjList; // 邻接表public KahnAlgorithm(int vertices) {this.vertices = vertices;adjList = new List[vertices];for (int i = 0; i < vertices; i++) {adjList[i] = new ArrayList<>();}}// 添加边public void addEdge(int src, int dest) {adjList[src].add(dest);}// Kahn算法实现的拓扑排序public void topologicalSort() {int[] inDegree = new int[vertices];for (int i = 0; i < vertices; i++) {for (int dest : adjList[i]) {inDegree[dest]++;}}Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < vertices; i++) {if (inDegree[i] == 0) {queue.add(i);}}List<Integer> topOrder = new ArrayList<>();while (!queue.isEmpty()) {int u = queue.poll();topOrder.add(u);for (int neighbor : adjList[u]) {if (--inDegree[neighbor] == 0) {queue.add(neighbor);}}}if (topOrder.size() != vertices) {System.out.println("图中存在环,无法进行拓扑排序");return;}System.out.println("拓扑排序结果:");for (int node : topOrder) {System.out.print(node + " ");}}public static void main(String[] args) {KahnAlgorithm graph = new KahnAlgorithm(6);graph.addEdge(0, 2);graph.addEdge(1, 2);graph.addEdge(1, 3);graph.addEdge(2, 4);graph.addEdge(3, 5);graph.addEdge(4, 5);graph.topologicalSort();}
}
代码注释
-
类和构造函数:
public class KahnAlgorithm {private int vertices; // 顶点数量private List<Integer>[] adjList; // 邻接表public KahnAlgorithm(int vertices) {this.vertices = vertices;adjList = new List[vertices];for (int i = 0; i < vertices; i++) {adjList[i] = new ArrayList<>();}}KahnAlgorithm类包含图的顶点数量和邻接表,并有一个构造函数来初始化这些变量。 -
添加边:
public void addEdge(int src, int dest) {adjList[src].add(dest); }addEdge方法用于向图中添加边。 -
Kahn算法的拓扑排序:
public void topologicalSort() {int[] inDegree = new int[vertices];for (int i = 0; i < vertices; i++) {for (int dest : adjList[i]) {inDegree[dest]++;}}Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < vertices; i++) {if (inDegree[i] == 0) {queue.add(i);}}List<Integer> topOrder = new ArrayList<>();while (!queue.isEmpty()) {int u = queue.poll();topOrder.add(u);for (int neighbor : adjList[u]) {if (--inDegree[neighbor] == 0) {queue.add(neighbor);}}}if (topOrder.size() != vertices) {System.out.println("图中存在环,无法进行拓扑排序");return;}System.out.println("拓扑排序结果:");for (int node : topOrder) {System.out.print(node + " ");} }topologicalSort方法实现了Kahn算法,进行拓扑排序。 -
主函数:
public static void main(String[] args) {KahnAlgorithm graph = new KahnAlgorithm(6);graph.addEdge(0, 2);graph.addEdge(1, 2);graph.addEdge(1, 3);graph.addEdge(2, 4);graph.addEdge(3, 5);graph.addEdge(4, 5);graph.topologicalSort(); }main方法创建一个图并进行拓扑排序。
Kahn算法的执行过程图解
基于DFS的算法
定义
基于DFS的拓扑排序算法通过递归的方式遍历图的每个顶点,并在访问完所有邻接顶点后将顶点压入栈中,最终从栈顶依次弹出顶点即为拓扑排序结果。
算法步骤
- 初始化:创建一个栈用于存储排序结果,标记所有顶点为未访问。
- DFS遍历:对于每个未访问的顶点,进行DFS遍历。递归访问该顶点的所有邻接顶点,访问完毕后将该顶点压入栈中。
- 构建排序:重复步骤2,直到所有顶点都被访问。最后从栈顶
依次弹出顶点即为拓扑排序结果。
示例
假设我们有一个有向无环图,顶点集合为 ({A, B, C, D, E, F}),边集合为 ({(A, C), (B, C), (B, D), (C, E), (D, F), (E, F)})。
基于DFS的算法实现
下面是用Java实现基于DFS的拓扑排序算法的代码示例:
import java.util.*;public class DFSTopologicalSort {private int vertices; // 顶点数量private List<Integer>[] adjList; // 邻接表public DFSTopologicalSort(int vertices) {this.vertices = vertices;adjList = new List[vertices];for (int i = 0; i < vertices; i++) {adjList[i] = new ArrayList<>();}}// 添加边public void addEdge(int src, int dest) {adjList[src].add(dest);}// 递归实现DFSprivate void DFS(int vertex, boolean[] visited, Stack<Integer> stack) {visited[vertex] = true;for (int neighbor : adjList[vertex]) {if (!visited[neighbor]) {DFS(neighbor, visited, stack);}}stack.push(vertex);}// 基于DFS的拓扑排序public void topologicalSort() {Stack<Integer> stack = new Stack<>();boolean[] visited = new boolean[vertices];for (int i = 0; i < vertices; i++) {if (!visited[i]) {DFS(i, visited, stack);}}System.out.println("拓扑排序结果:");while (!stack.isEmpty()) {System.out.print(stack.pop() + " ");}}public static void main(String[] args) {DFSTopologicalSort graph = new DFSTopologicalSort(6);graph.addEdge(0, 2);graph.addEdge(1, 2);graph.addEdge(1, 3);graph.addEdge(2, 4);graph.addEdge(3, 5);graph.addEdge(4, 5);graph.topologicalSort();}
}
代码注释
-
类和构造函数:
public class DFSTopologicalSort {private int vertices; // 顶点数量private List<Integer>[] adjList; // 邻接表public DFSTopologicalSort(int vertices) {this.vertices = vertices;adjList = new List[vertices];for (int i = 0; i < vertices; i++) {adjList[i] = new ArrayList<>();}}DFSTopologicalSort类包含图的顶点数量和邻接表,并有一个构造函数来初始化这些变量。 -
添加边:
public void addEdge(int src, int dest) {adjList[src].add(dest); }addEdge方法用于向图中添加边。 -
递归实现DFS:
private void DFS(int vertex, boolean[] visited, Stack<Integer> stack) {visited[vertex] = true;for (int neighbor : adjList[vertex]) {if (!visited[neighbor]) {DFS(neighbor, visited, stack);}}stack.push(vertex); }DFS方法递归访问顶点及其邻接顶点,并在访问完所有邻接顶点后将顶点压入栈中。 -
基于DFS的拓扑排序:
public void topologicalSort() {Stack<Integer> stack = new Stack<>();boolean[] visited = new boolean[vertices];for (int i = 0; i < vertices; i++) {if (!visited[i]) {DFS(i, visited, stack);}}System.out.println("拓扑排序结果:");while (!stack.isEmpty()) {System.out.print(stack.pop() + " ");} }topologicalSort方法实现了基于DFS的拓扑排序,输出拓扑排序结果。 -
主函数:
public static void main(String[] args) {DFSTopologicalSort graph = new DFSTopologicalSort(6);graph.addEdge(0, 2);graph.addEdge(1, 2);graph.addEdge(1, 3);graph.addEdge(2, 4);graph.addEdge(3, 5);graph.addEdge(4, 5);graph.topologicalSort(); }main方法创建一个图并进行拓扑排序。
基于DFS算法的执行过程图解
结论
通过上述讲解和实例代码,我们详细展示了Kahn算法和基于DFS的拓扑排序算法的定义、步骤及其实现。希望这篇博客对您有所帮助!
如果您觉得这篇文章对您有帮助,请关注我的CSDN博客,点赞并收藏这篇文章,您的支持是我持续创作的动力!
关键内容总结:
- Kahn算法的定义和实现
- 基于DFS的拓扑排序算法的定义和实现
- 两种算法的执行过程图解
推荐阅读:深入探索设计模式专栏,详细讲解各种设计模式的应用和优化。点击查看:深入探索设计模式。
特别推荐:设计模式实战专栏,深入解析设计模式的实际应用,提升您的编程技巧。点击查看:设计模式实战。
如有任何疑问或建议,欢迎在评论区留言讨论。谢谢阅读!
相关文章:
拓扑排序:Kahn算法与DFS算法
引言 拓扑排序是有向无环图(DAG)中的一种线性排序,使得对于图中的每一条有向边 ( u \rightarrow v ),顶点 ( u ) 在排序中出现在顶点 ( v ) 之前。本文将详细介绍两种实现拓扑排序的算法:Kahn算法和基于深度优先搜索&…...
图像处理 -- Sobel滤波器的实现原理与使用案例
Sobel滤波器 概述 Sobel滤波器是一种边缘检测方法,用于图像处理和计算机视觉领域。它通过计算图像灰度值的梯度来检测边缘。Sobel滤波器结合了高斯平滑和微分操作,以减少噪声并增强边缘检测效果。 实现原理 Sobel滤波器通过使用两个3x3卷积核&#x…...
机器学习 第10章-降维与度量学习
机器学习 第10章-降维与度量学习 10.1 k近邻学习 k近邻(k-Nearest Neighbor,简称kNN)学习是一种常用的监督学习方法其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个“邻居”的信息来进行预测。通…...
linux驱动:(7)物理地址到虚拟地址映射
单片机、裸机、linux操控硬件方法 在单片机和裸机中操作硬件是通过指针来对寄存器赋值来进行操控 但对于linux中不能这样,不能直接对物理地址直接修改,因为linux使能了mmu,所以不能直接菜操作物理地址 如果要操作硬件,需要先把…...
浏览器用户文件夹详解 - Preferences(十)
1.Preferences简介 1.1 什么是Preferences文件? Preferences文件是Chromium浏览器中用于存储用户个性化设置和配置的一个重要文件。每当用户在浏览器中更改设置或安装扩展程序时,这些信息都会被记录在Preferences文件中。通过这些记录,浏览…...
Robot Operating System——电池电量通知
大纲 应用场景定义字段解释 案例 sensor_msgs::msg::BatteryState 是 ROS 2 中定义的消息类型,用于表示电池状态。它包含了电池电量、电压、电流、温度等信息。 应用场景 机器人 电池监控:在移动机器人中,电池是主要的电源。BatteryState 消…...
二进制安装docker
目录 一、准备 Docker CE 二进制包 二、解压.tgz包 三、复制二进制文件到/usr/bin/目录 四、创建用户组 五、配置相关服务配置文件 六、拷贝配置文件到指定目录 七、启动 dockerd 服务进程 八、shell脚本一键安装 一、准备 Docker CE 二进制包 https://download.docker…...
@SpringBootConfiguration重复加载报错
Junit单元测试Test启动报错,SpringBootConfiguration注解重复问题排查: SpringBootApplication 注解的 exclude 属性用于排除特定的自动配置类,而不是用于排除主配置类本身。因此,不能通过 exclude 属性来排除主配置类的加载。 …...
【SpringBoot】数据验证之分组校验
分组校验 在不同情况下,可能对JavaBean对象的数据校验规则有所不同,有时需要根据数据状态对JavaBean中的某些属性字段进行单独验证。这时就可以使用分组校验功能,即根据状态启用一组约束。 Hibernate Validator的注解提供了groups参数&#…...
MySQL Galera Cluster 部署与介绍
目录 主要特点 组件 一. 环境准备 二. 配置 1. 配置 galera1 主机的my.cnf的文件 2. 配置 galera2 主机的my.cnf的文件 3. 配置 galera3 主机的my.cnf的文件 4. 在给galera1 主机的my.cnf的文件增加节点 5. 写入数据验证同步 6. 配置 galera4 主机的my.cnf的文件 M…...
RuoYi-Vue-Plus (XXL-JOB任务调度中心二:配置管理与定时任务编写、执行策略、命令行任务、邮件报警等等
一、后端xxl job的配置属性介绍 enabled : 是否开启执行器,如果为false,调度中心就调用不了后端定时任务admin-addresses:调度中心的地址,多个则可以逗号拼接: url1,url2,url3access-token: 执行器通讯TOKEN ,必须和x…...
【docker】虚拟化与docker基础
一、虚拟化 1.虚拟化概述 什么是虚拟化? 虚拟化:将应用程序和系统内核资源进行解耦,以操作系统级别进行隔离,目的是提高资源利用率 2、虚拟化的功能 将虚拟化的性能优化趋近于物理资源的性能,主要用于提高资源利用…...
Vue3安装ffmpeg做视频截取报错
通过 yarn 安装 ffmpeg 时报错。 即,执行以下指令时报错: yarn add ffmpeg/ffmpeg^0.10.0 yarn add ffmpeg/core^0.10.0错误信息: node_modules\pngquant-bin: Command failed. Error: pngquant failed to build, make sure that libpng-d…...
如何在 Java 中实现自定义的排序算法?
在Java中实现自定义排序算法的步骤如下: 创建一个类,实现Java的Comparator接口,该接口包含一个compare方法,用于比较两个对象的大小。在compare方法中,根据自定义的排序规则,比较两个对象的大小并返回-1、…...
【Homebrew】brew 命令
Brew(也称为Homebrew)是Mac OS上的一款包管理器,它允许用户通过简单的命令行界面来安装、更新、卸载和管理软件包。以下是一些常用的Brew命令及其功能说明: 安装与卸载 安装Brew 命令(适用于大多数用户,可…...
【https】无法安装OpenSSL时如何在局域网开通https服务
【背景】 做Stream传输服务,需要用到fetch方法,所以自然也需要https服务。 公司的开发机由于某些管理上的原因无法直接安装openssl for win的安装包。 【分析】 没有命令行工具,就试试看万能的python包吧,直接安装cryptography包。 pip install cryptography【方法】 …...
OpenGL实现3D游戏编程【连载1】——初探3D世界
1、前言 在我学习C的过程中,研究了一下OpenGL编程,打开了3D世界的编程世界,3D世界的效果还是相当不错。而且OpenGL能够支持跨平台兼容,是不错的学习方向,于是就自己学习了网上的很多教程,并将所有学到的知…...
工程化实践:工程配置化设计
文内项目 Github:XIAOJUSURVEY 配置化是很灵活且很常见的使用,那XIAOJUSURVEY里有哪些地方应用到了呢? 基础模板 问卷模板 在创建问卷时,我们提供了多种问卷类型选择,例如普通问卷、投票、报名、NPS等。 为了实…...
浏览器事件循环详解
1. 浏览器的进程模型 1.1. 何为进程? 程序运行需要有它自己的专属内存空间,可以把这块内存空间简单的理解为进程。 每个应用至少有一个进程,进程之间相互独立,即使要通信,也需要双方同意。 1.2. 何为线程?…...
Linux:线程管理(线程创建、线程退出、线程回收、线程分离、其它线程函数)
线程管理 (1)What(什么是线程管理) 对程序中线程的创建、调度、同步、退出、回收等操作进行有效的控制和协调 (2)Why(为什么要管理线程) 充分利用系统资源,提高程序的并发的性能和稳定性。但如果管理不当,…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
