928 字
5 分钟
判断图中是否存在环

前言#

最近面试突然被问到数据结构学得怎样,我说还行,大学数据结构99分(学校不好,考得简单),结果直接来一道 判断图中是否存在环 把我整懵圈,工作多年都没接触过,以前做过的算法题里,刚好图这个数据结构没做过一道题,完全忘了里面的结构,什么邻接表全忘了,把我尴尬死,后面都不敢说话了

邻接表#

先简单回顾一下什么是邻接表,其实就是用来记录顶点旁边有谁的一个二维结构

例如无向图中,A 的旁边有 B,B 的旁边有 A

graph = {
'A': ['B'],
'B': ['A']
}

有向图,A 的旁边有 B,B 的旁边没有顶点

directed_graph = {
'A': ['B'],
'B': []
}

同理,无向和有向加权图,只是多了权重

weighted_graph = {
'A': [('B', 5)],
'B': [('A', 5)]
}
weighted_directed_graph = {
'A': [('B', 3)],
'B': []
}

数据结构#

以无向图为例

class Graph {
/** 顶点数 */
private int V;
/** 邻接表 */
private List<List<Integer>> adj;
public Graph(int v) {
V = v;
adj = new ArrayList<>(v);
for (int i = 0; i < v; i++) {
adj.add(new ArrayList<>());
}
}
public void addEdge(int u, int v) {
// u 的旁边有 v,v 的旁边有 u
adj.get(u).add(v);
adj.get(v).add(u);
}
}

解决思路#

至少需要三条边才能成环,写出一个最简单成环的邻接表,符号示意:A - B - C - A

graph = {
'A': ['B'],
'B': ['A', 'C'],
'C': ['B', 'A']
}

观察顶点 C 最后一个邻接顶点 A 有什么特性,首先它肯定是被访问过的

但顶点 B 中的邻接顶点 A 也同样被访问过,它们有什么区别?


因为这是无向图,A - B 在邻接表中会被记录成 A 的旁边有 B,B 的旁边有 A,这不会形成环

说明:当 A 的旁边(B)的旁边是 A 自己,就不会形成环

那反过来如果不是自己,是不是就会形成环?回到顶点 C 最后一个邻接顶点 A

它旁边(C)的旁边是顶点 B,不是它自己,确实会形成环

邻接顶点旁边的旁边其实就是当前顶点的父顶点

总结

在无向图中,判断图中是否存在环,需要满足两个条件

  • 当前邻接顶点已被访问
  • 当前顶点的父顶点不等于自己

通过深度优先搜索(DFS)或广度优先搜索(BFS)遍历图,找到符合条件的顶点即可

DFS#

public boolean hasCycle() {
// 记录顶点是否访问过
boolean[] visited = new boolean[V];
for (int i = 0; i < V; i++) {
if (!visited[i]) {
// 起始节点没有父节点
if (dfs(i, visited, -1)) {
return true;
}
}
}
return false;
}
private boolean dfs(int v, boolean[] visited, int parent) {
visited[v] = true;
// 遍历当前顶点的所有邻接顶点
for (int neighbor : adj.get(v)) {
if (!visited[neighbor]) {
// 如果邻接顶点未被访问过,递归调用 dfs 方法
if (dfs(neighbor, visited, v)) {
return true;
}
} else if (neighbor != parent) {
// 如果邻接顶点已被访问过且不是当前顶点的父顶点,说明存在环
return true;
}
}
return false;
}

BFS#

public boolean hasCycle() {
boolean[] visited = new boolean[V];
int[] parent = new int[V];
Arrays.fill(parent, -1);
for (int i = 0; i < V; i++) {
if (!visited[i]) {
if (bfs(i, visited, parent)) {
return true;
}
}
}
return false;
}
private boolean bfs(int v, boolean[] visited, int[] parent) {
Queue<Integer> queue = new LinkedList<>();
queue.add(v);
visited[v] = true;
while (!queue.isEmpty()) {
// 遍历当前节点的所有邻居
int currentVertex = queue.poll();
for (int neighbor : adj.get(currentVertex)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
// 记录父节点,入队
parent[neighbor] = currentVertex;
queue.add(neighbor);
// 已访问的邻居,如果不是父节点,说明存在环
} else if (neighbor != parent[currentVertex]) {
return true;
}
}
}
return false;
}
判断图中是否存在环
https://cloop.zone.id/posts/algorithm/has-cycle-in-graph/
作者
Cloop
发布于
2025-05-09
许可协议
CC BY-NC-SA 4.0