拓扑排序

图的存储

  • 矩阵存储 graph = new int[n][n]
  • 链表存储 List> grapth = new ArrayList<>()

辅助存储数据结构:

  • 入度数组 inDeg = new int[n]
  • 遍历队列 queue = new LinkedList<>()

    207-课程表 I

    image.png
    题意分析:

  • 课程表的先后修关系构造图,本题的目的就是判断图中是否存在环。

解题思路:

  • 拓扑排序。根据课程关系构造图,并记录每个节点的入度值,然后根据拓扑排序遍历节点,最后判断是否每个节点都被访问过(如果有节点没有被访问说明图中存在环)。

注意点:
扩展:
代码:

  1. public class CoursesCanFinish_207 {
  2. /**
  3. * 题解分析:问题的本质就是检查有向图中是否存在环
  4. *
  5. * 解题思路:
  6. * 1、根据 int[][] 构建有向图。初始化入度。
  7. * 2、对图进行拓扑排序。访问的节点入度 -1。
  8. * 3、结束判断条件:队列为空,是否所有节点都被访问。
  9. *
  10. * @param numCourses
  11. * @param prerequisites
  12. * @return
  13. */
  14. public boolean canFinish(int numCourses, int[][] prerequisites) {
  15. // 构建图的矩阵存储
  16. List<List<Integer>> graph = new ArrayList<>();
  17. for (int i = 0; i < numCourses; i++) {
  18. graph.add(new ArrayList<>());
  19. }
  20. // 记录节点入度
  21. int[] inDeg = new int[numCourses];
  22. // 记录节点是否访问
  23. boolean[] visited = new boolean[numCourses];
  24. Arrays.fill(visited, false);
  25. // 初始化矩阵数据
  26. for (int[] arr: prerequisites) {
  27. int v = arr[1], w = arr[0];
  28. graph.get(v).add(w);
  29. ++inDeg[w];
  30. }
  31. // System.out.println("graph = " + graph);
  32. // 从入度为0的顶点开始遍历
  33. Queue<Integer> queue = new LinkedList<>();
  34. for (int i = 0; i < numCourses; i++) {
  35. if (inDeg[i] == 0) {
  36. queue.offer(i);
  37. }
  38. }
  39. // System.out.println("indeg = " + Arrays.toString(inDeg));
  40. while (!queue.isEmpty()) {
  41. int curr = queue.poll();
  42. visited[curr] = true;
  43. // 获取当前节点的直连节点
  44. for (int ele : graph.get(curr)) {
  45. if (--inDeg[ele] == 0) {
  46. queue.offer(ele);
  47. }
  48. }
  49. }
  50. // System.out.println("visited = " + Arrays.toString(visited));
  51. for (int i = 0; i < numCourses; i++) {
  52. if (visited[i] == false) {
  53. return false;
  54. }
  55. }
  56. return true;
  57. }
  58. public static void main(String[] args) {
  59. CoursesCanFinish_207 obj = new CoursesCanFinish_207();
  60. int numCourses = 3;
  61. int[][] prerequisites = {{1,0}, {2,0}, {2,1}};
  62. System.out.println("ret = " + obj.canFinish(numCourses, prerequisites));
  63. }
  64. }

210-课程表 II

image.png
题意分析:

  • 遍历图,并按课程先后顺序输出课程顺序。

解题思路:

  • 拓扑排序。遍历图并记录遍历节点最后输出。

注意点:
扩展:
代码:

  1. public class CoursesFindOrder_210 {
  2. /**
  3. * 本题的关键就是对图进行拓扑排序。
  4. * @param numCourses
  5. * @param prerequisites
  6. * @return
  7. */
  8. public int[] findOrder(int numCourses, int[][] prerequisites) {
  9. // 构建图的矩阵存储
  10. List<List<Integer>> graph = new ArrayList<>();
  11. for (int i = 0; i < numCourses; i++) {
  12. graph.add(new ArrayList<>());
  13. }
  14. // 记录节点入度
  15. int[] inDeg = new int[numCourses];
  16. // 记录节点是否访问
  17. boolean[] visited = new boolean[numCourses];
  18. Arrays.fill(visited, false);
  19. // 记录访问节点路径
  20. int[] ans = new int[numCourses];
  21. int idx = 0;
  22. // 初始化矩阵数据
  23. for (int[] arr: prerequisites) {
  24. int v = arr[1], w = arr[0];
  25. graph.get(v).add(w);
  26. ++inDeg[w];
  27. }
  28. // System.out.println("graph = " + graph);
  29. // 从入度为0的顶点开始遍历
  30. Queue<Integer> queue = new LinkedList<>();
  31. for (int i = 0; i < numCourses; i++) {
  32. if (inDeg[i] == 0) {
  33. queue.offer(i);
  34. }
  35. }
  36. // System.out.println("indeg = " + Arrays.toString(inDeg));
  37. while (!queue.isEmpty()) {
  38. int curr = queue.poll();
  39. visited[curr] = true;
  40. ans[idx++] = curr;
  41. // 获取当前节点的直连节点
  42. for (int ele : graph.get(curr)) {
  43. if (--inDeg[ele] == 0) {
  44. queue.offer(ele);
  45. }
  46. }
  47. }
  48. // System.out.println("visited = " + Arrays.toString(visited));
  49. for (int i = 0; i < numCourses; i++) {
  50. if (visited[i] == false) {
  51. return new int[0];
  52. }
  53. }
  54. return ans;
  55. }
  56. public static void main(String[] args) {
  57. CoursesFindOrder_210 obj = new CoursesFindOrder_210();
  58. int numCourses = 4;
  59. int[][] prerequisites = {{1,0}, {2,0}, {3,1}, {1,2}};
  60. System.out.println("ret = " + Arrays.toString(obj.findOrder(numCourses, prerequisites)));
  61. }
  62. }

802-找到最终的安全状态

image.png
题意分析:

  • 图中没有构成环的节点。(判断有环的问题如何解决呢?拓扑排序)

解题思路:

  • 图反向后拓扑排序。入度为 0 的节点为安全节点,删除入度为 0 到直连节点指向,剩余入度为 0 的节点也是安全节点。

注意点:

  • 图的反向构造,并记录节点的入度。

扩展:
代码:

  1. /**
  2. * 解题思路:
  3. * 1、初始出度为 0 节点都是安全节点;
  4. * 2、删除初始出度为 0 的节点,剩余出度为 0 的节点也为安全节点,递归该步骤。
  5. *
  6. * 因此,将有向图反向,入度为 0 的节点为安全节点,这就成了 拓扑排序(BFS + 贪心算法,解决有向图是否有环问题)
  7. */
  8. public class EventualSafeNodes_802 {
  9. public List<Integer> eventualSafeNodes(int[][] graph) {
  10. int n = graph.length;
  11. // 将图反向,用链表存储
  12. List<List<Integer>> rg = new ArrayList<>();
  13. // 记录每个节点的入度
  14. int[] inDeg = new int[n];
  15. // 初始化反向图
  16. for (int i = 0; i < n; i++) {
  17. rg.add(new ArrayList<>());
  18. }
  19. // 填充反向图
  20. for (int i = 0 ; i < n; i++) {
  21. for (int j = 0; j < graph[i].length; j++) {
  22. rg.get(graph[i][j]).add(i);
  23. }
  24. // 原数组记录的节点出度,在反图中就是入度
  25. inDeg[i] = graph[i].length;
  26. }
  27. // System.out.println("rg = " + rg.toString());
  28. // System.out.println("inDeg = " + Arrays.toString(inDeg));
  29. Queue<Integer> queue = new LinkedList<>();
  30. // 初始化入度为 0 的节点
  31. for (int i = 0; i < n; i++) {
  32. if (inDeg[i] == 0) {
  33. queue.offer(i);
  34. }
  35. }
  36. while (!queue.isEmpty()) {
  37. int cur = queue.poll();
  38. // 分别将 x 节点的直连边节点入度 -1
  39. for (int x : rg.get(cur)) {
  40. if (--inDeg[x] == 0) {
  41. queue.offer(x);
  42. }
  43. }
  44. }
  45. // System.out.println("new inDeg = " + Arrays.toString(inDeg));
  46. List<Integer> ans = new ArrayList<>();
  47. for (int i = 0; i < n; ++i) {
  48. if (inDeg[i] == 0) {
  49. ans.add(i);
  50. }
  51. }
  52. return ans;
  53. }
  54. public static void main(String[] args) {
  55. EventualSafeNodes_802 obj = new EventualSafeNodes_802();
  56. // int[][] graph = {{1,2},{2,3},{5},{0},{5},{},{}};
  57. int[][] graph = {{1,2,3,4},{1,2},{3,4},{0,4},{}};
  58. System.out.println("ans = " + obj.eventualSafeNodes(graph));
  59. }
  60. }

图的遍历(BFS/DFS,可达性分析)

1462-课程表 IV

image.png
题意分析:

  • 给定图中两个节点,判断两个节点是否可达,可达则说明 a 是 b 的先修课程。

解题思路:

  • 图中从给定节点开始的 bfs 遍历。

注意点:
扩展:

  • dfs 遍历如何实现?

代码:

  1. /**
  2. * 有向无环图的遍历
  3. */
  4. public class CoursesCheckIfPrerequisite_1462 {
  5. public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
  6. // 构建临接表存储图节点
  7. List<List<Integer>> graph = new ArrayList<>();
  8. for (int i = 0; i < numCourses; i++) {
  9. graph.add(new ArrayList<>());
  10. }
  11. for (int[] arr : prerequisites) {
  12. int v = arr[0], w = arr[1];
  13. graph.get(v).add(w);
  14. }
  15. // 存储结果数据
  16. List<Boolean> ans = new ArrayList<>();
  17. for (int i = 0; i < queries.length; i++) {
  18. // 判断顶点 start 到 end 是否可达
  19. int start = queries[i][0], end = queries[i][1];
  20. // System.out.println("start = " + start + ", end = " + end);
  21. ans.add(bfs(graph, start, end));
  22. }
  23. // System.out.println("ret = " + ans.toString());
  24. return ans;
  25. }
  26. /**
  27. * bfs 算法判断两个节点是否可达
  28. * @param graph
  29. * @param start
  30. * @param end
  31. * @return
  32. */
  33. public boolean bfs(List<List<Integer>> graph, int start, int end) {
  34. Queue<Integer> queue = new LinkedList<>();
  35. boolean[] visited = new boolean[graph.size()];
  36. Arrays.fill(visited, false);
  37. queue.offer(start);
  38. while (!queue.isEmpty()) {
  39. int curr = queue.poll();
  40. visited[curr] = true;
  41. for (int ele : graph.get(curr)) {
  42. if (ele == end) {
  43. // System.out.println(start + " to " + end + " is connected");
  44. return true;
  45. }
  46. if (visited[ele] == false) {
  47. queue.offer(ele);
  48. }
  49. }
  50. }
  51. return false;
  52. }
  53. /**
  54. * 输入:n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]
  55. * 输出:[true,false,true,false]
  56. *
  57. * 来源:力扣(LeetCode)
  58. * 链接:https://leetcode-cn.com/problems/course-schedule-iv
  59. * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
  60. */
  61. public static void main(String[] args) {
  62. CoursesCheckIfPrerequisite_1462 obj = new CoursesCheckIfPrerequisite_1462();
  63. // int numCourses, int[][] prerequisites, int[][] queries
  64. int numCourses = 5;
  65. int[][] prerequisites = {{0,1}, {1,2}, {2,3}, {3,4}};
  66. int[][] queries = {{0,4}, {4,0},{1,3},{2,4}};
  67. System.out.println(obj.checkIfPrerequisite(numCourses, prerequisites, queries));
  68. }
  69. }

路径问题(路径和/最短路径)

743-网络延迟时间(节点k到其他节点最短距离中的最大值)

image.png
题意分析:

  • 节点 k 到其他节点的最短距离,然后从所有最短距离中取出最大值即为最长信号发送时间。

解题思路:

  • Dijkstra 最短路径算法。采用贪心策略求节点 k 到其他节点的最短距离。

注意点:

  • 矩阵存储(数组存储 graph[n][n],课程表问题是用链表存储 List> graph = new ArrayList<>())

扩展:
代码:

  1. public class NetworkDelayTime_743 {
  2. public int networkDelayTime(int[][] times, int n, int k) {
  3. int INF = Integer.MAX_VALUE / 2;
  4. // 矩阵存储图节点
  5. int[][] graph = new int[n][n];
  6. // 初始化矩阵,距离都为最大值
  7. for (int i = 0; i < n; i++) {
  8. Arrays.fill(graph[i], INF);
  9. }
  10. // 根据图两个节点直达关系,更新 graph 数据
  11. for (int[] arr : times) {
  12. // 数据下标和节点值不完全对应,特殊处理一下。比如节点 1-5,数组下标 0-4
  13. int v = arr[0] - 1, w = arr[1] - 1, val = arr[2];
  14. graph[v][w] = val;
  15. }
  16. System.out.println("-----初始化矩阵数据后-----------");
  17. printArray(graph);
  18. // 顶点 k 到其他节点的最短距离
  19. int[] dist = new int[n];
  20. // 顶点 k 到其他节点的最短距离是否已经找到
  21. boolean[] flag = new boolean[n];
  22. // 初始化数据
  23. for (int i = 0; i < n; i++) {
  24. dist[i] = graph[k-1][i];
  25. flag[i] = false;
  26. }
  27. // 对顶点 k 自身进行初始化
  28. dist[k-1] = 0;
  29. flag[k-1] = true;
  30. System.out.println("-------初始化访问信息---------");
  31. System.out.println("dist=" + Arrays.toString(dist));
  32. System.out.println("flag=" + Arrays.toString(flag));
  33. int vs = - 1;
  34. // 保证 k 到每个顶点都被访问到
  35. for (int i = 0; i < n; i++) {
  36. // 寻找当前最小的路径,即,从未获取最短路径的顶点中,找到距离 k 最近的顶点 vs
  37. int min = INF;
  38. for (int j = 0; j < n; j++) {
  39. if (flag[j] == false && dist[j] < min) {
  40. min = dist[j];
  41. vs = j;
  42. }
  43. }
  44. System.out.println("当前访问节点:" + vs);
  45. if (vs == -1) {
  46. return -1;
  47. }
  48. // 标记顶点 vs 为已经获取到的最短路径
  49. flag[vs] = true;
  50. // 更新顶点 k 到其他未访问节点的最短距离
  51. for (int j = 0; j < n; j++) {
  52. dist[j] = Math.min(dist[j], dist[vs] + graph[vs][j]); // 加法数组越界
  53. }
  54. }
  55. System.out.println();
  56. System.out.println("-------更新后节点访问信息---------");
  57. System.out.println("dist=" + Arrays.toString(dist));
  58. System.out.println("flag=" + Arrays.toString(flag));
  59. int ans = Arrays.stream(dist).max().getAsInt();
  60. System.out.println("ans=" + ans);
  61. return ans == INF ? -1 : ans;
  62. }
  63. public void printArray(int[][] arr) {
  64. for (int i = 0; i < arr.length; i++) {
  65. for (int j = 0; j < arr[0].length; j++) {
  66. System.out.print(arr[i][j] + " ");
  67. }
  68. System.out.println();
  69. }
  70. }
  71. public static void main(String[] args) {
  72. NetworkDelayTime_743 obj = new NetworkDelayTime_743();
  73. int[][] times = {{2,1,1}, {2,3,1}, {3,4,1}};
  74. // int[][] times = {{1,2,1}};
  75. int n = 4;
  76. int k = 2;
  77. System.out.println("return = " +obj.networkDelayTime(times,n,k));
  78. }
  79. }