本篇文章主要介绍「环检测」和「拓扑排序」的相关内容
在看本文章之前,最好先去看完文章 图的遍历。对于「无环图的遍历」「有环图的遍历」 以及「如何构建图的邻接表」在其中都有提及
顾名思义,环检测就是判断一个图是否存在环,题目详情可见 课程表
这里介绍两种方式:「DFS」和「BFS」。关于「DFS」详细内容可见 回溯 (DFS) 算法框架,关于「BFS」详细内容可见 BFS 算法框架
// 是否有环的标志private boolean hasCycle = false;// 记录节点访问情况private boolean[] visited;// 记录是否在路径中private boolean[] onPath;public boolean canFinish(int numCourses, int[][] prerequisites) { // 见「图的遍历」文章 List<Integer>[] graph = buildGraph(prerequisites, numCourses); visited = new boolean[numCourses]; onPath = new boolean[numCourses]; // 可能存在非连通图 for (int i = 0; i < numCourses; i++) { if (!visited[i]) dfs(graph, i); } return !hasCycle;}private void dfs(List<Integer>[] graph, int s) { // 先序:首次进入当前处理节点 // 判断阶段 // 判断当前要处理的节点是否在 onPath 中 // 如果在,则存在环 if (onPath[s]) hasCycle = true; // 判断是否访问过 以及 是否已经检测出了环 if (visited[s] || hasCycle) return ; // 标记 visited[s] = true; onPath[s] = true; // 遍历阶段 for (int v : graph[s]) { dfs(graph, v); } // 后序:离开当前节点 // 把节点从 onPath 中 onPath[s] = false;}原理:入度为 0 的节点即可加入拓扑序列中
xxxxxxxxxxpublic boolean canFinish(int numCourses, int[][] prerequisites) { List<Integer>[] graph = buildGraph(prerequisites, numCourses); int[] inDegree = new int[numCourses]; // init inDegree for (int[] courses : prerequisites) { inDegree[courses[0]]++; } Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { // 把入度为 0 的节点加入队列中 if (inDegree[i] == 0) q.offer(i); } int count = 0; while (!q.isEmpty()) { int node = q.poll(); count++; for (int v : graph[node]) { inDegree[v]--; if (inDegree[v] == 0) q.offer(v); } } return count == numCourses;}节点间存在先后关系,以一种保证节点间先后顺序的方式来排序。只有当节点的先行节点完成后,当前节点才可以进入处理阶段
如上图所示,该图的拓扑排序为:A -> B -> C -> E -> D -> F -> G
解释:G 必须在 E,F 完成后才可以进行
强调:存在环的图没有拓扑排序
题目详情可见 课程表 II
拓扑排序是后序遍历的倒序
原因:后序遍历的特点:先遍历左右节点,后遍历根节点;同时从叶子节点开始逐渐向上
而拓扑排序的特点:先访问根后,才可以访问孩子节点
xxxxxxxxxx// 记录后序遍历的顺序private List<Integer> postOrder;private boolean[] visited;private boolean[] onPath;private boolean hasCycle = false;public int[] findOrder(int numCourses, int[][] prerequisites) { postOrder = new ArrayList<>(); visited = new boolean[numCourses]; onPath = new boolean[numCourses]; List<Integer>[] graph = buildGraph(prerequisites, numCourses); for (int i = 0; i < numCourses; i++) dfs(graph, i); // 存在环,无拓扑排序 if (hasCycle) return new int[]{}; // 反转顺序 Collections.reverse(postOrder); int[] res = new int[postOrder.size()]; for (int i = 0; i < postOrder.size(); i++) { res[i] = postOrder.get(i); } return res;}private void dfs(List<Integer>[] graph, int s) { if (onPath[s]) { hasCycle = true; } if (visited[s] || hasCycle) return; visited[s] = true; onPath[s] = true; for (int v : graph[s]) { dfs(graph, v); } // 后序阶段:加入 List 中 postOrder.add(s); onPath[s] = false;}xxxxxxxxxxpublic int[] findOrder(int numCourses, int[][] prerequisites) { List<Integer>[] graph = buildGraph(prerequisites, numCourses); int[] inDegree = new int[numCourses]; // init inDegree for (int[] courses : prerequisites) { inDegree[courses[0]]++; } Queue<Integer> q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { // 把入度为 0 的节点加入队列中 if (inDegree[i] == 0) q.offer(i); } int count = 0; int[] res = new int[numCourses]; while (!q.isEmpty()) { int node = q.poll(); // 区别 1 res[count++] = node; for (int v : graph[node]) { inDegree[v]--; if (inDegree[v] == 0) q.offer(v); } } // 区别 2 if (count != numCourses) return new int[]{}; return res;}