第六课 图 相关练习题,拓扑排序

208 阅读2分钟

拓扑排序简介:

  • 使用的算法思想:广度优先遍历、贪心算法

  • 应用场景:有前置条件的。任务调度计划、课程安排,eg:《机器学习》的先导课程为《高等数学》;

  • 作用:

    • 得到一个拓扑序,但不唯一。

    • 检测有向图是否有环。

      • 补充:无向图,检测是否有环,使用的数据结构是并查集

207. Course Schedule 课程表1

有两个解法、分别是BFS(拓扑排序、结合贪心),DFS(老方法)

拓扑排序:

 class Solution {
     
     public boolean canFinish(int numCourses, int[][] prerequisites) {
         ArrayList<Integer>[] graph = new ArrayList[numCourses];
         int[] inDegree = new int[numCourses];  
         // inDegree 用来存储每个节点的入度。模拟删除节点。 c++代码中,有将这个封装进node里面
         for (int i = 0; i < numCourses; i++) {
             graph[i] = new ArrayList<>();
         }
         // build graph;
         for (int[] edge : prerequisites) {
             int from = edge[1];
             int to = edge[0];
             graph[from].add(to);
             inDegree[to]++;
         }
         
         int count = 0; // 利用count 去计算是否全部 结点已经变为 0 入度
         Queue<Integer> queue = new LinkedList<>();
         for (int i = 0; i < numCourses; i++) {
             if (inDegree[i] == 0) {
                 queue.offer(i);
             }
         }
         
         while (!queue.isEmpty()) {
             int peek = queue.poll();
             count++;
             for (int v : graph[peek]) {
                 inDegree[v]--;
                 if (inDegree[v] == 0) {
                     queue.offer(v);
                 }
             }
         }
         
         if (count == numCourses) {
             return true;
         }
         return false;
     }
 }

DFS

 class Solution {
     
     private List<List<Integer>> edges; 
     private boolean[] visited;
     
     private boolean[] onPath;
     private boolean hasCycle;
     
     public boolean canFinish(int numCourses, int[][] prerequisites) {
         
         visited = new boolean[numCourses];
         onPath = new boolean[numCourses];
         
         edges = new ArrayList<>(numCourses);
         for (int i = 0; i < numCourses; i++) {
             edges.add(new ArrayList<>());
         }
         for (int[] edge : prerequisites) {
             edges.get(edge[1]).add(edge[0]);
         }
         
         for (int i = 0; i < numCourses; i++) {
             dfs(i);
         }
         return !hasCycle;
     }
     
     public void dfs(int s) {
         if (onPath[s]) {
             hasCycle = true;
             return;
         }
         if (visited[s]) return;
         visited[s] = true;
         onPath[s] = true;
         for (int i = 0; i < edges.get(s).size(); i++) {
             dfs(edges.get(s).get(i));
         }
         onPath[s] = false;
     }
 }

210. 课程表 II

广度优先遍历、贪心算法

 class Solution {
     public int[] findOrder(int numCourses, int[][] prerequisites) {
 ​
         ArrayList<Integer>[] graph = new ArrayList[numCourses];
         for (int i = 0; i < numCourses; i++) {
             graph[i] = new ArrayList<>();
         }
 ​
         // 维护一个入度的数组。 // 用来判断 结点入度为0时 可以加入队列。
         int[] inDegree = new int[numCourses];
         for (int[] v : prerequisites) {
             int from = v[1];
             int to = v[0];
             graph[from].add(to);
             inDegree[to]++;
         }
 ​
         Queue<Integer> queue = new LinkedList<>();
         for (int i = 0; i < numCourses; i++) {
             if (inDegree[i] == 0) {
                 queue.offer(i);
             }
         }
 ​
         int[] res = new int[numCourses];
         int count = 0;
 ​
         while (!queue.isEmpty()) {
             int now = queue.poll();
             res[count] = now;
             count++;
             for (int i = 0; i < graph[now].size(); i++) {
                 inDegree[graph[now].get(i)]--;
                 if (inDegree[graph[now].get(i)] == 0) {
                     queue.offer(graph[now].get(i));
                 }
             }
         }
 ​
         if (count == numCourses) {
             return res;
         }
         return new int[0];
     }
 }

先说最重要的部分:

  • 「拓扑排序」是专门应用于有向图的算法;
  • 这道题用 BFS 和 DFS 都可以完成,只需要掌握 BFS 的写法就可以了,BFS 的写法很经典;
  • BFS 的写法就叫「拓扑排序」,这里还用到了贪心算法的思想,贪的点是:当前让入度为 0 的那些结点入队;
  • 「拓扑排序」的结果不唯一;
  • 删除结点的操作,通过「入度数组」体现,这个技巧要掌握;
  • 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环,这个知识点非常重要,如果在面试的过程中遇到这个问题,要把这一点说出来。
  • 具有类似附加功能的算法还有:Bellman-Ford 算法附加的作用是可以用于检测是否有负权环(在这里不展开了,我也不太熟)。