如何在 Java 中实现无向环和有向环的检测
无向环
一个含有环的无向图如下所示,其中有两个环,分别是 0-2-1-0 和 2-3-4-2:

要检测无向图中的环,可以使用深度优先搜索。假设从顶点 0 出发,再走到相邻的顶点 2,接着走到顶点 2 相邻的顶点 1,由于顶点 0 和顶点 1 相邻,并且顶点 0 被标记过了,说明我们饶了一圈,所以无向图中存在环。虽然顶点 2 和顶点 1 相邻,但是并不能说明存在环,因为我们就是从顶点 2 直接走到顶点 1 的,这二者只有边的关系。算法如下所示:
package com.zhiyiyo.graph;
import com.zhiyiyo.collection.stack.LinkStack;
import com.zhiyiyo.collection.stack.Stack;
/**
* 无向图中的环
*/
public class Cycle {
private boolean[] marked;
private Graph graph;
private boolean hasCycle;
public Cycle(Graph graph) {
this.graph = graph;
marked = new boolean[graph.V()];
for (int v = 0; v < graph.V(); ++v) {
if (!marked[v]) {
dfs(v);
}
}
}
private void dfs(int s) {
if (hasCycle()) return;
Stack<Integer> vertexes = new LinkStack<>();
vertexes.push(s);
marked[s] = true;
int lastVertex = s;
while (!vertexes.isEmpty()) {
int v = vertexes.pop();
for (int w : graph.adj(v)) {
if (!marked[w]) {
marked[w] = true;
vertexes.push(w);
} else if (w != lastVertex) {
hasCycle = true;
return;
}
}
lastVertex = v;
}
}
/**
* 图中是否有环
*/
public boolean hasCycle() {
return hasCycle;
}
}
有向环
有向图
有向图的实现方式和上一篇博客 《如何在 Java 中实现无向图》 中无向图的实现方式几乎一样,只是在添加边 v-w 时只在顶点 v 的链表上添加顶点 w,而不对顶点 w 的链表进行操作。如果把 LinkGraph 中成员变量的访问权限改成 protected,只需继承并重写 addEdge 方法即可:
package com.zhiyiyo.graph;
public class LinkDigraph extends LinkGraph implements Digraph {
public LinkDigraph(int V) {
super(V);
}
@Override
public void addEdge(int v, int w) {
adj[v].push(w);
E++;
}
@Override
public Digraph reverse() {
Digraph digraph = new LinkDigraph(V());
for (int v = 0; v < V(); ++v) {
for (int w : adj(v)) {
digraph.addEdge(w, v);
}
}
return digraph;
}
}
检测算法
一个含有有向环的有向图如下所示,其中 5-4-3-5 构成了一个环:

这里使用递归实现的深度优先搜索来检测有向环。假设从顶点 0 开始走,一路经过 5、4、3 这三个顶点,最终又碰到了与顶点 3 相邻的顶点 5,这时候如果知道顶点 5 已经被访问过了,并且递归函数还被压在栈中,就说明深度优先搜索从顶点 5 开始走,又回到了顶点 5,也就是找到了有向环。算法如下所示:
package com.zhiyiyo.graph;
import com.zhiyiyo.collection.stack.LinkStack;
import com.zhiyiyo.collection.stack.Stack;
/**
* 有向图中的环
*/
public class DirectedCycle {
private boolean[] marked;
private boolean[] onStack;
private int[] edgeTo;
private Graph graph;
private Stack<Integer> cycle;
public DirectedCycle(Digraph graph) {
this.graph = graph;
marked = new boolean[graph.V()];
onStack = new boolean[graph.V()];
edgeTo = new int[graph.V()];
for (int v = 0; v < graph.V(); ++v) {
if (!marked[v]) {
dfs(v);
}
}
}
private void dfs(int v) {
marked[v] = true;
onStack[v] = true;
for (int w : graph.adj(v)) {
if (hasCycle()) return;
if (!marked[w]) {
marked[w] = true;
edgeTo[w] = v;
dfs(w);
} else if (onStack[w]) {
cycle = new LinkStack<>();
cycle.push(w);
for (int i = v; i != w; i = edgeTo[i]) {
cycle.push(i);
}
cycle.push(w);
}
}
onStack[v] = false;
}
/**
* 图中是否有环
*/
public boolean hasCycle() {
return cycle != null;
}
/**
* 图中的一个环
*/
public Iterable<Integer> cycle() {
return cycle;
}
}
如何在 Java 中实现无向环和有向环的检测的更多相关文章
- 如何在 Java 中实现最小生成树算法
定义 在一幅无向图 \(G=(V,E)\) 中,\((u, v)\) 为连接顶点 \(u\) 和顶点 \(v\) 的边,\(w(u,v)\) 为边的权重,若存在边的子集 \(T\subseteq E\ ...
- 如何在JAVA中实现一个固定最大size的hashMap
如何在JAVA中实现一个固定最大size的hashMap 利用LinkedHashMap的removeEldestEntry方法,重载此方法使得这个map可以增长到最大size,之后每插入一条新的记录 ...
- 如何在java中使用sikuli进行自动化测试
很早之前写过一篇介绍sikuli的文章.本文简单介绍如何在java中使用sikuli进自动化测试. 图形脚本语言sikuli sikuli IDE可以完成常见的单击.右击.移动到.拖动等鼠标操作,ja ...
- 如何在Java中调用Python代码
有时候,我们会碰到这样的问题:与A同学合作写代码,A同学只会写Python,而不会Java, 而你只会写Java并不擅长Python,并且发现难以用Java来重写对方的代码,这时,就不得不想方设法“调 ...
- 如何在java中跳出当前多重嵌套循环?有几种方法?
如何在java中跳出当前多重嵌套循环?有几种方法? - 两种方法 - 1.在外层循环定义标记 ok: for(int i=0;i<100;i++){ ...
- 用代码说话:如何在Java中实现线程
并发编程是Java语言的重要特性之一,"如何在Java中实现线程"是学习并发编程的入门知识,也是Java工程师面试必备的基础知识.本文从线程说起,然后用代码说明如何在Java中实现 ...
- 如何在Java中测试类是否是线程安全的
通过优锐课的java核心笔记中,我们可以看到关于如何在java中测试类是否线程安全的一些知识点汇总,分享给大家学习参考. 线程安全性测试与典型的单线程测试不同.为了测试一个方法是否是线程安全的,我们需 ...
- 如何在pyqt中自定义无边框窗口
前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...
- 如何在pyqt中给无边框窗口添加DWM环绕阴影
前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...
随机推荐
- 微服务从代码到k8s部署应有尽有大结局(k8s部署)
我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...
- hive从入门到放弃(二)——DDL数据定义
前一篇文章,介绍了什么是 hive,以及 hive 的架构.数据类型,没看的可以点击阅读:hive从入门到放弃(一)--初识hive 今天讲一下 hive 的 DDL 数据定义 创建数据库 CREAT ...
- 30 面向对象编程 抽象类 abstract 修饰符可以用来修饰方法也可以修饰类
抽象类 概念 abstract 修饰符可以用来修饰方法也可以修饰类, 如果修饰方法,那么该方法就是抽象方法: 如果修饰类,那么该类就是抽象类. 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为 ...
- Java运算符 算术运算法
运算符 算术运算法:+,-,*,/,%,++,– 复制运算符:= 关系运算符:>,<,>=,<=,==,!= instanceof 逻辑运算符:&&,||,! ...
- 【面经】MySql常见问题
1. 数据库三范式是什么? 1. 第一范式(1NF):字段具有原子性,不可再分.(所有关系型数据库系统都满足第一范式数据库表中的字段都是单一属性的,不可再分) 2. 第二范式(2NF)是在第一范式(1 ...
- spring——整合Mybatis
一.Mybatis整合spring 步骤: 导入相关jar包 junit mybatis mysql数据库 spring-webmvc aop织入 mybatis-spring spring-jdbc ...
- FPGA设计流程
今天学习了FPGA设计流程的视频,我理解要做一个完整的FPGA系统,所要经历的步骤,先将它简单总结如下: 我在对上面的流程图进行解释: 第一:设计定义就是我们这个FPGA系统或者FPGA设计所要实现的 ...
- 洛谷P1049 [NOIP2001 普及组] 装箱问题
本题就是一个简单的01背包问题 1.因为每个物品只能选一次,而且要使箱子的剩余空间为最小.所以可以确定属性为 MAX 2.由于是从n个物品里面选i个物品 那么就是选出的i个物品的空间总和要尽可能的 ...
- springMVC的执行流程?
springMVC是由dispatchservlet为核心的分层控制框架.首先客户端发出一个请求web服务器解析请求url并去匹配dispatchservlet的映射url,如果匹配上就将这个请求放入 ...
- MySQL 获取每月多少日的sql写法
# 写法1 指定 年月 的共有多少日 select DATEDIFF(DATE_ADD(CONCAT( 2020, '-', '03','-','01'),INTERVAL 1 MONTH),CONC ...