【BFS 解决拓扑排序】课程表

文章目录

题目链接:207. 课程表


题目解析

拓扑排序 (Topological sorting )要解决的问题是 如何给一个有向无环图的所有节点排序。

有向无环图 (Directed Acyclic Graph, 缩写 DAG),是一种 边有方向且没有环形结构的图结构。

拓扑排序的起始点是 顶点(入度为0的节点),将顶点排序完毕后,从该顶点发出的有向边全部会被删除,这使得被指向的节点的入度数会减1,当入度数为0时这个节点就成为新了的顶点,然后下一轮从该顶点开始排序,以此重复,直到没有顶点为止。

  • 入度:表示指向某节点的有向边数量,
  • 出度:表示从该节点发出的有向边数量

构造拓扑序列步骤:

  1. 从图中选择一个入度为零的点
  2. 输出该顶点,从图中删除此顶点及其所有的出边

重复上面两步,直到所有顶点都输出,拓扑排序完成,或者图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁。

例子:做一道 青椒炒肉 的过程图如下

按照流程图中的顺序一步一步做出来这道菜的过程,就相当于是 拓扑排序 的过程。

拓扑排序的目标是将所有节点排序,可能出现多个不同的排序结果:

  • 买菜 ---> 准备厨具 ---> 洗菜 ---> 腌肉 ---> 切菜 ---> 炒菜 ---> 装盘
  • 准备厨具 ---> 买菜 ---> 洗菜 ---> 腌肉 ---> 切菜 ---> 炒菜 ---> 装盘
  • 准备厨具 ---> 买菜 ---> 洗菜 ---> 切菜 ---> 腌肉 ---> 炒菜 ---> 装盘
  • 准备厨具 ---> 腌肉 ---> 买菜 ---> 洗菜 ---> 切菜 ---> 炒菜 ---> 装盘

如何实现拓扑排序?借助队列,一次多源 BFS 即可:

  1. 先将所有入度为0的节点放入队列
  2. 层序遍历:先拿出队首元素并添加到结果中,然后将从该队首元素发出的边全部删去,再判断与这些边相连的节点的入度减一之后的值是否为0(若为0就再加入队列中)

题目给出一个 numCourses 表示本学期要修读的课程数,记作 0 - numCourses-1。

在选修某些课程之前需要一些先修课程。先修课程由数组 prerequisites 给出,其中 prerequisites[i] = [a, b] 表示如果要学习课程 a 则 必须 先学习课程 b。

我们需要判断能否完成所有课程的学习。

例1:numCourses = 2, prerequisites = [[1,0]]

prerequisites[0] = [1, 0] 表示要学习课程 1 必须先学习课程 0,而总课程数 numCourses = 2,这两门课程可能就是课程 1 和课程 0,因此可以先学习课程 0 再学习课程 1 ,是可能完成所有课程的学习的,因此返回 true。

例2:numCourses = 5, prerequisites = [[1,0], [2,0], [3,0], [3,1], [3,2], [4,3]]

学习课程的顺序可以是:0 ---> 1 ---> 2 ---> 3 ---> 4,因此是可以完成所有课程的学习的,返回 true。


算法原理

例:numCourses = 5, prerequisites = [[1,0], [2,0], [3,0], [3,1], [3,2], [4,3]]

根据题目所给,可以画出:

我们需要判断的 "能否完成所有课程的学习" 其实就是判断 有向无环图中是否存在环 ------ 即 能否进行拓扑排序。

因此我们可以用拓扑排序来解决本题。

构造拓扑序列步骤:

  1. 从图中选择一个入度为零的点
  2. 输出该顶点,从图中删除此顶点及其所有的出边

重复上面两步,直到所有顶点都输出,拓扑排序完成,或者图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁。

建图

在构造拓扑序列之前,首先要根据题目抽象出图结构。

我们用 邻接表 来表示图的结构,代码实现有两种方式:

  • 对于字符串类型的数据,我们通常用 哈希表 (Map < String, List < String >> edges)作为邻接表来映射 "节点,相连的节点列表"。
  • 对于整数类型的数据,我们通常用 链表(List< List< Integer >> edges)作为邻接表,用下标(前提是数据从0开始计数)作为 "节点",链表的值作为 "与该节点相连的节点列表"。(也可以使用哈希表来映射:Map < Integer, List < Integer >> edges)

入度数组

本题我们在进行拓扑排序的过程中还需要知道节点的入度值,因此我们为了方便,可以用一个入度数组 in 来记录图中所有节点的入度值。

java 复制代码
int[] in = new int[numCourses];

代码实现

java 复制代码
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // <顶点, 连接的节点列表>
        Map<Integer, List<Integer>> edges = new HashMap<>(); // 邻接表存放图
        int[] in = new int[numCourses];                      // 用于记录每一个顶点的入度值

        // 建图
        for (int[] prerequisite : prerequisites) {
            // prerequisites[a][b] -> 要学习a必须先学习b -> b是a的前提/b有一条路径指向a
            int a = prerequisite[0], b = prerequisite[1]; // b -> a
            if (!edges.containsKey(b)) {         // 判断邻接表中是否存在顶点(b)
                edges.put(b, new ArrayList<>()); // 将顶点存入邻接表中
            }
            edges.get(b).add(a);  // 将与顶点连接的节点添加到节点列表
            in[a]++;              // 更新连接b节点的入度值
        }

        // 拓扑排序(用来判断图中是否存在环:存在-true/不存在-false)
        Queue<Integer> queue = new ArrayDeque<>();  // 队列用于存放入度值为0的顶点
        // 1.将入度值为0的顶点放入队列
        for (int x = 0; x < numCourses; x++) {
            if (in[x] == 0) {
                queue.offer(x);
            }
        }
        // 2.层序遍历/BFS
        while (!queue.isEmpty()) {
            int top = queue.poll(); // 取出队首元素
            // 遍历队首元素对应的节点列表,将列表中的所有节点的入度值减一(删除连接线)
            for (int x : edges.getOrDefault(top, new ArrayList<>())) {
                // 将入度值减一之后判断入度值是否为0(为0时是顶点,要放入队列)
                if (--in[x] == 0) {
                    queue.offer(x);
                }
            }
        }

        // 判断图中是否存在环(若入度数组in的所有值都为0则图中不存在环)
        for (int x : in) {
            if (x != 0) {
                // 不为0,有环
                return false;
            }
        }

        // 返回true
        return true;
    }
}

完

相关推荐
Crazy________3 小时前
05Linux内存管理核心原理与运维实战
java·开发语言
Nebula_g4 小时前
JavaSE拓展:工具类Executors
java·开发语言·后端·spring·基础·javase
扶风ff4 小时前
练题簿在线免费刷题:会员线下组卷、Word 试卷与成绩导入,课堂检测更方便
java·开发语言·算法·小程序·word
鱼宵4 小时前
Spring AI 可观测与透明化:traceId 串起全链路,思考过程实时直播给用户
java·人工智能·spring·链路追踪·springai
yi0114 小时前
LeetCode 15:三数之和|排序 + 双指针,如何避免重复答案?
笔记·python·算法·leetcode·双指针
Raas1004 小时前
AI网关能做语义缓存吗?MAI Gateway(魔芋企业级AI网关)实战能力深度解读
java·后端·spring
天空鸟_时光不老4 小时前
13-亮点提炼:把技术说辞翻译成业务价值
java·spring boot·spring·spring cloud·mybatis
-dzk-4 小时前
【多维动态规划】LC 1143.最长公共子序列
算法·动态规划
小蒜学长4 小时前
基于SpringBoot+Vue的租房管理系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·租房管理