第一次写博客,不太会用,话不多说 直接上代码 详细可以看注释,无向图判断是否存在环比有向图相对复杂一点 ,需要判断访问的节点的临接表中的节点与父节点是否相同。
/**
* @Description:判断无向图是否有环 深度优先遍历
* 需要保存父节点
* @Create 2020-04-03 21:04
* @Email:1173748742@qq.com
*/
public class IsHaveLoop {
public static void main(String[] args) {
IsHaveLoop isHaveLoop = new IsHaveLoop();
int[][] graph = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {2, 4}};
int n = 5;
boolean haveLoop = isHaveLoop.isHaveLoop(graph, n);
System.out.println(haveLoop);
}
/**
* @param graph 图的连接边
* @param n 图的节点个数
* @return 是否存在环
*/
public boolean isHaveLoop(int[][] graph, int n) {
//这段代码可要可不要,可以提前判断结果
// if (graph.length >= n) {//当边数大于节点的个数时,必定存在环 自己可以画图推导
// return true;
// }
//习惯上转换成临接表的形式
List<Integer>[] adj = new ArrayList[n];
for (int[] edg : graph) {
int node1 = edg[0];
int node2 = edg[1];
if (adj[node1] == null) {
adj[node1] = new ArrayList<>();
}
if (adj[node2] == null) {
adj[node2] = new ArrayList<>();
}
adj[node1].add(node2);
adj[node2].add(node1);
}
boolean[] visited = new boolean[n];//定义一个节点状态数组 判断是否访问过
int[] a = {0};
for (int i = 0; i < n; i++) {
if (visited[i] == false) {//如果没有进行访问 则进行深度优先搜索回溯
dfsCycle(adj, i, -1, visited, a);//引用传递 函数内部修改值后退出函数可见
// System.out.println(a[0]);
if (a[0] == 1) {//只要有一次i循环时存在环路那就直接提前返回,说明存在环
return true;
}
}
}
return a[0] == 1;
}
/**
* @param adj 图的临接表
* @param current 当前节点
* @param parent 父节点
* @param visited 判断是否访问
* @param flag 是否存在环
*/
private void dfsCycle(List<Integer>[] adj, int current, int parent, boolean[] visited, int[] flag) {
visited[current] = true;//首先 访问当前节点 并进行标记
List<Integer> list = adj[current]; //获取到当前节点能够到达的所有节点
for (Integer can : list) {
if (visited[can] == false) {//如果节点没有被访问过
dfsCycle(adj, can, current, visited, flag);//当前节点就是父节点,循环的节点就是子节点
} else if (can != parent) {// 在节点被访问过的情况下 如果该节点不等于父节点 ,说明有环
flag[0] = 1;
}
//循环节点等于父节点的情况直接跳过,不用处理
}
}
}
更新2020-8-17
添加一个使用并查集的方法。如果没有了解过并查集的话可以推荐看看这个视频,灯神讲的真好。思想就是不断对两个边进行联合,如果能够联合,这说明这条边加上去不能使集合产生环,继续向后遍历,如果存在一条边在进行联合时,没办法联合,说明会产生环。下边贴一下java代码。有的位置加了注释应该可以理解。
package com.atschool.algorithm;
import java.util.Arrays;
/**
* @Description:查看无向图中有没有环
* @Create 2020-08-17 14:42
* @Email:1173748742@qq.com
*/
public class DisjoinSet {
private int[] parent;
private int[] rank;
/**
* 这里要求节点编号从0开始进行编号
* 如果不是的话就不可以用数组来作为map,可能需要用两个HashMap才行
*
* @param edges 边
* @param n 节点个数
* @return
*/
public boolean detecteCycle(int[][] edges, int n) {
parent = new int[n];
Arrays.fill(parent, -1);
rank = new int[n];
for (int i = 0; i < edges.length; i++) {
int x = edges[i][0];
int y = edges[i][1];
if (!union(x, y)) {
return true;
}
}
return false;
}
/**
* 连接节点
*
* @param x
* @param y
* @return
*/
private boolean union(int x, int y) {
int x_root = find_root(x);
int y_root = find_root(y);
if (x_root == y_root) {
return false; // 连接失败 // 说明有环
} else {
// 可以进行连接
// 下边的判断是为了降低树的高度 一个小优化
if (rank[x_root] > rank[y_root]) {
parent[y_root] = x_root;
} else if (rank[x_root] < rank[y_root]) {
parent[x_root] = y_root;
} else {
parent[x_root] = y_root;
rank[y_root]++;
}
return true;
}
}
/**
* 找到根节点函数
*
* @param x
* @return
*/
private int find_root(int x) {
int x_root = x;
while (parent[x_root] != -1) {
x_root = parent[x_root];
}
return x_root;
}
public static void main(String[] args) {
DisjoinSet disjoinSet = new DisjoinSet();
int[][] edges = {{0, 1}, {1, 2}, {1, 3}, {3, 4}, {2, 4}, {4, 5}}; // 有环
// int[][] edges = {{0, 1}, {1, 2}, {1, 3}, {3, 4}, {4, 5}}; 无环
boolean b = disjoinSet.detecteCycle(edges, 6);
if (b) {
System.out.println("detected cycle !");
} else {
System.out.println("no cycle!");
}
}
}