焦茶/夏コミ本に収録です 小宇宙
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;}