有向图和无向图的环检测
1.无向图
深度优先搜索:思想是,DFS过程,记录当前结点的父结点,如果某结点已经被访问过且不是当前结点的父结点,则存在环
并查集:如果两个结点存在一条边,判断两个结点的在并查集中的根结点是否相同,不相同则表明在不同的连通块,把他们union到一个连通块;
否则,在遍历过程中发现如果某条边的两个结点都在一个连通块,则表明存在环
package dfs;
//冗余连接是指root[x] == root[y]
class Solution684 {
class Union{
private int[] root;
private int[] path;
Union(int n){
root = new int[n];
path = new int[n];
for(int i=0;i<n;i++){
root[i] = i;
}
}
public boolean union(int x,int y){
int rootX = find(x);
int rootY = find(y);
if(rootX==rootY){
return false;
}
if(path[rootX]<path[rootY]){ //将小树的根结点连接到大树的根结点
root[rootX] = rootY;
path[rootY] += 1;
}else{
root[rootY] = rootX;
path[rootX] += 1;
}
return true;
}
public int find(int x){
if(x != root[x]){
root[x] = find(root[x]);
}
return root[x];
}
}
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
Union ufind = new Union(n+1); //结点是从1开始的所以加1
for(int[] edge:edges){
if(!ufind.union(edge[0],edge[1])){
return edge;
}
}
return edges[0];
}
}
public class lc684 {
public static void main(String[] args) {
int[][] edges = {{1,2},{1,3},{2,3}};
Solution684 s = new Solution684();
int[] res = s.findRedundantConnection(edges);
System.out.println(res[0]+" "+res[1]);
}
}
/*
c++版本
class Solution {
int u[2000];
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
for(int i=0;i<edges.size();i++){
u[i] = i;
}
vector<int> result(2,0);
for(int i=0;i<edges.size();i++){
vector<int> edge = edges[i];
int root_x = find(edge[0]);
int root_y = find(edge[1]);
if(root_x == root_y){
result[0] = edge[0];
result[1] = edge[1];
}else{
combine(edge[0],edge[1]);
}
}
return result;
}
int find(int x){
while(x!=u[x]){
u[x] = u[u[x]];
x = u[x];
}
return u[x];
}
void combine(int x,int y){
u[find(x)] = find(y);
}
};
* */
2.有向图
深度优先搜索:
设置一个visited数组表示结点被访问过,如果在遍历过程中发现被访问过,则可能存在环;也存在被访问过但没有环的情况,所以需要借助栈,每当遍历一个结点,将结点推至栈中,结点是后进先出的,也就是
如果环入口的结点会更靠近栈底,只有当结点被遍历过,且位于栈中,则判定有环;当该结点所有的邻居结点遍历完成,或者退回至该结点,将该结点出栈,这样做的目的是为了保证防止出现假的环
package dfs;
import java.util.ArrayList;
import java.util.List;
/*
* 0 - 3
* / \ \ |
* 1 - 2 4
* */
class Solution207 {
private List<Integer>[] g_list;
//思路,用stack保存哪些结点在栈上,如果同时在栈上且被访问过,则有环
public boolean canFinish(int numCourses, int[][] prerequisites) {
boolean[] visited = new boolean[numCourses];
boolean[] stack = new boolean[numCourses];
g_list = new ArrayList[numCourses];
//构建邻接表
for(int i=0;i<numCourses;i++){
g_list[i] = new ArrayList<>();
}
for(int[] z : prerequisites){
g_list[z[0]].add(z[1]);
}
for(int i=0;i<numCourses;i++){
if(!visited[i]){
if(dfs(i,visited,stack)){
return false;
}
}
}
return true;
}
private boolean dfs(int v,boolean[] visited,boolean[] stack){
visited[v] = true;
stack[v] = true;
for(int i : g_list[v]){
if(visited[i] && stack[i]){
return true;
}
if(visited[i]) {
continue;
}
if(dfs(i,visited,stack)){
return true;
}
}
stack[v] = false;
return false;
}
}
public class lc207 {
public static void main(String[] args) {
int numCourses = 2;
int[][] prerequisites = new int[][]{{1,0}};
Solution207 s = new Solution207();
boolean res = s.canFinish(numCourses,prerequisites);
System.out.println(res);
}
}
使用拓扑排序,
拓扑排序的主要步骤
(1)将入度为0的节点入队列
(2)每次弹出队列的头元素
(3)遍历该节点出发的变,并将重点节点的入度减1
(4)如果节点的入度为0, 则加入队列
(5)重复(2)(3)(4)
ArrayList<ArrayList<Integer>> graph ;
int[] degree = new int[5001];
public boolean toposort(int n){
Queue<Integer> q = new LinkedList<>();
//将所有入度为0的点如队列
for(int i=0;i<n;i++){
if(degree[i]==0){
q.offer(i);
}
}
//开始拓扑排序
while(!q.isEmpty()){
//取入度为0的点
int cur = q.poll();
//删除其与其他顶点的连边,同时这些顶点入度减一
for(int x:graph.get(cur)){
degree[x]--;
//判断顶点的度是否为0,为0则加入队列
if(degree[x]==0){
q.offer(x);
}
}
}
//判断是否还有结点度不为0, 如果有则存在环
for (int i=0;i<n;i++){
if(degree[i] != 0){
return true;
}
}
return false;
}
//https://leetcode-cn.com/problems/circular-array-loop/solution/gtalgorithm-tuo-bu-zai-xian-by-gtalgorit-ah46/
java 队列
Queue<Integer> q = new LinkedList();
添加元素至队尾, q.offer(1)
取队列头部元素并删除队列头 q.poll()