Detect Cycle in a Directed Graph in Java29 Mar 2025 | 7 分钟阅读 有向图中的环检测是图论核心问题的一个变种,它经常用于依赖解析、调度以及某些游戏算法。环实际上是一个闭合路径,也就是一条从某个节点出发又回到该节点的路径。 在有向图的情况下,由于边的方向会使环检测变得复杂,情况会有些复杂。本文将介绍使用 Java 通过 深度优先搜索 (DFS) 和基于 广度优先搜索 的 Kahn 算法来检测有向图中的环的问题。 问题概述考虑到有向图,我们要分析的问题在于找出是否存在环。表示图有两种方式:邻接表或邻接矩阵,为了便于理解,我们将使用邻接表。 示例输入: 我们将使用被称为“三角形”的图,顶点为 {0 -> 1, 1 -> 2, 2 -> 0}。 输出: 检测到环。 问题解决方案方法 1:深度优先搜索 (DFS)这意味着 DFS 算法适合检测有向图中的环。为了查找递归,我们将节点标记为已访问节点;如果一个节点再次被访问,并且该节点位于递归栈中,则存在环。 算法初始化
DFS 遍历
End
文件名:GraphCycleDetection.java 输出 Cycle detected. 方法 2:使用 Kahn 算法 (拓扑排序 - BFS)另一种用于检测有向图中的环的算法基于拓扑排序。图中的环表明无法实现有效的拓扑排序。现在,根据顶点的入度,并在我们逐步消除入度为零的顶点时,我们可以判断是否存在环。 算法入度计算: 每个顶点还必须标记其传入边的数量。 BFS 遍历: 以入度为零的顶点开始迭代,并减少与之关联的顶点的入度。 检查结果: 如果所有顶点都已处理,则存在环,否则不存在。 文件名:GraphCycleDetectionKahns.java 输出 Cycle detected. 注意事项和边缘情况
效率和性能DFS 方法: 最著名的算法的时间复杂度为 O(V + E),其中 V 是顶点数,E 是边数。空间复杂度在这种情况下等同于递归栈,因此为 O(V)。 Kahn 算法: 时间复杂度也为 O(V + E),边和顶点都会被处理。由于使用了队列和入度数组,空间复杂度也为 O(V)。 环检测的应用1. 操作系统中的死锁检测死锁是操作系统中一个令人头疼的问题,可以通过使用不同的算法来检测它来克服。 它们列出了四种情况,并解释了为什么在操作系统中进程有时需要能够单独访问某些资源:如果一组进程在等待由其他进程拥有的资源时获取资源,则会发生死锁。 为了处理 OC4,将环检测应用于进程/进程资源请求/分配,将其视为有向图,以检测潜在的死锁。如果存在环,则可能发生死锁,因为每个进程都在无限地等待其他进程。 2. 软件构建系统使用的去中心化在自动化构建系统(Maven、Gradle、npm 等)中,项目和库通常依赖于其他库。当存在循环引用,一个库依赖于另一个库,而另一个库又依赖于第一个库时,这会在构建过程中造成问题。 更具体地说,环检测可用于找出此类环,从而使包的编译和管理得以顺利进行。 3. 任务调度和工作流系统的实现计划任务的系统(可能包括作业调度或项目管理工具)中的活动可能相互依赖(例如,任务 B 在任务 A 结束之前无法开始)。这些依赖关系可以很好地用有向图表示。 如果顶点 A 到另一个顶点 B 有一条边,而顶点 B 又到顶点 A 有一条边,那么这就是一个环,任务永远无法完成。采用一种称为环检测的技术来确保活动不能以产生环的方式进行计划。 4. 编译器和链接器编译器依赖于依赖图来识别程序中不同模块和文件之间的依赖关系。例如,一个函数或一个变量可能依赖于另一个。 当文件或模块之间存在环时(也就是说,如果我们有一个模块 A 包含模块 B,而模块 B 包含模块 A),编译或链接就会中断。为了使源代码能够被编译和链接,在这样的图中,循环检测至关重要。 结论在有向图中识别环在广泛的应用中具有显著优势,例如 操作系统死锁 识别、软件构建管理、任务调度以及编译器和版本控制系统。 我们探讨了两种有效的方法:DFS 和 Kahn 算法。这两种技术都非常适合;然而,如果使用基于递归的操作,则 DFS 因其易于使用的遍历技术而更适合。 另一方面,Kahn 算法通过拓扑排序,在问题可以转化为寻找一个可行的排序(其中多个任务或进程施加了某些关系)的情况下会更容易。 哪种方法应该被使用没有固定的规则,因为它取决于所使用的应用程序类型和要分析的图的类型。例如,在任务调度系统中,通常使用 Kahn 算法进行拓扑排序;而在检测操作系统死锁的情况下,由于其递归性,DFS 更为合适。 同样,依赖管理也涉及这两种方法,具体取决于所涉及软件系统的依赖图的大小和结构。 下一个主题Java 中的幂函数 |
Dijkstra 算法是查找源节点到目标节点最短路径的著名算法之一。它使用贪心方法来查找最短路径。Dijkstra 算法的概念是从...开始查找最短距离(路径)
阅读 8 分钟
Java 运算符是一个特殊的符号,它对多个操作数执行特定的操作并输出结果。Java 有大量的运算符,它们分为两类。第一,运算符的性能基于其操作数的数量...
阅读 3 分钟
在 Java 编程中,null 的概念既基本又无处不在。它代表了引用类型值的缺失,并且是开发人员处理未初始化对象或数组情况的关键工具。理解 null 对于...至关重要。
阅读 3 分钟
在这里,将使用 java.lang 包中的 Runtime 类。因为每个 Java 程序都有一个 Runtime 类的实例,所以这个类允许 Java 应用程序改变其执行环境。让我们看看 Runtime 类的 exec() 方法,看看任务可能如何...
阅读 4 分钟
? 在本文中,我们将学习 Java 中的项目是什么以及如何在集成开发环境 (IDE) 中创建它们。JAVA 项目主题将帮助我们更好地使用 Java 并使用 Java 构建工作应用程序。让我们...
阅读 4 分钟
在编程中,循环是一系列重复执行的指令,直到满足某个条件。在本节中,我们将通过示例讨论 Java 中的带标签循环。什么是 Java 中的带标签循环?标签是一个有效的变量名,它表示...
阅读 2 分钟
Y 形链表是一种链表,其中两条不同的链表在共享的交叉点处相遇。在此 Java 程序中,我们说明了如何确定两条链表汇合的交叉点。该方法包括遍历...
14 分钟阅读
内置的 Java 函数 java.util.concurrent.atomic.AtomicInteger.toString() 会生成当前存储在该整数中的值的字符串表示形式。AtomicIntegerArray 的 toString() 函数生成的字符串表示数组的当前值。因为它使得查看内容变得容易...
阅读 2 分钟
组合设计模式是一种设计模式,它允许我们将对象排列成树形结构来表示部分-整体设计。它允许客户精确地处理单个项目和包。简单来说,它允许我们将单个对象与...
5 分钟阅读
在编程中,将一种类型转换为另一种类型(或反之)是一项至关重要的任务。有时我们需要将一种类型转换为另一种类型。在 Java 转换部分,我们讨论了各种类型的转换。在本节中,我们可以讨论如何将二进制转换为...
5 分钟阅读
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India