news 2026/10/21 21:51:00

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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来记录图中所有节点的入度值。

int[]in=newint[numCourses];

代码实现

classSolution{publicbooleancanFinish(intnumCourses,int[][]prerequisites){// <顶点, 连接的节点列表>Map<Integer,List<Integer>>edges=newHashMap<>();// 邻接表存放图int[]in=newint[numCourses];// 用于记录每一个顶点的入度值// 建图for(int[]prerequisite:prerequisites){// prerequisites[a][b] -> 要学习a必须先学习b -> b是a的前提/b有一条路径指向ainta=prerequisite[0],b=prerequisite[1];// b -> aif(!edges.containsKey(b)){// 判断邻接表中是否存在顶点(b)edges.put(b,newArrayList<>());// 将顶点存入邻接表中}edges.get(b).add(a);// 将与顶点连接的节点添加到节点列表in[a]++;// 更新连接b节点的入度值}// 拓扑排序(用来判断图中是否存在环:存在-true/不存在-false)Queue<Integer>queue=newArrayDeque<>();// 队列用于存放入度值为0的顶点// 1.将入度值为0的顶点放入队列for(intx=0;x<numCourses;x++){if(in[x]==0){queue.offer(x);}}// 2.层序遍历/BFSwhile(!queue.isEmpty()){inttop=queue.poll();// 取出队首元素// 遍历队首元素对应的节点列表,将列表中的所有节点的入度值减一(删除连接线)for(intx:edges.getOrDefault(top,newArrayList<>())){// 将入度值减一之后判断入度值是否为0(为0时是顶点,要放入队列)if(--in[x]==0){queue.offer(x);}}}// 判断图中是否存在环(若入度数组in的所有值都为0则图中不存在环)for(intx:in){if(x!=0){// 不为0,有环returnfalse;}}// 返回truereturntrue;}}

完

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 21:50:47

PHP四大加密技术全解析+实战笔记免费领

在PHP语言里, 加密的操作方式主要有那四种常用的办法。除了刚才提到的这几个方法以外, 还有一种跟网址链接相关的加密处理以及解密的方式是另外一种存在的情况。我希望这段文字内容对于你们正在进行的软件开发工作能够提供一点实际的帮助作用和使用价值参考。 另外, 我把之后整…

作者头像 李华
网站建设 2026/10/18 22:12:26

气源含水对气液增压器的故障影响有多大?选台湾钰腾从根源规避风险

很多机加工车间都有一个常年被忽略的隐形隐患&#xff1a;气源里的水。尤其是南方梅雨季、北方冬季温差大的时段&#xff0c;空压机出来的压缩空气里混着大量冷凝水&#xff0c;哪怕装了普通的油水分离器&#xff0c;也很难完全过滤干净。这些混在气源里的水顺着管路钻进气液增…

作者头像 李华
网站建设 2026/10/18 3:03:52

OSI与TCP/IP分层模型实战:从理论到网络故障排查

计算机网络这门课&#xff0c;很多人是把它当“死知识”来背的&#xff1a;OSI七层是哪七层、TCP/IP有哪几个协议、三次握手发几个包……背得滚瓜烂熟&#xff0c;一旦真碰上网络故障&#xff0c;照样不知道从哪下手。我带过的不少新人都有这个问题。究其原因&#xff0c;是没有…

作者头像 李华
网站建设 2026/10/16 6:55:25

视觉SLAM第7讲|特征点法、对极几何与位姿求解全梳理

视觉SLAM第7讲&#xff5c;视觉里程计(上)&#xff1a;特征点法、对极几何与位姿求解全梳理 如果说前面几讲的刚体运动、李群李代数是SLAM的「数学工具包」&#xff0c;那从这一讲开始&#xff0c;我们就正式进入视觉SLAM的核心前端模块——视觉里程计&#xff08;Visual Odome…

作者头像 李华
网站建设 2026/10/19 19:33:49

Linux auditd系统审计实战:规则配置与安全事件追踪方法

auditd安装与基本配置 auditd是Linux内核审计子系统的用户空间守护进程&#xff0c;提供系统级安全审计能力。服务器安全加固场景中&#xff0c;auditd能够记录文件访问、系统调用、用户权限变更等关键操作&#xff0c;是合规审计和入侵检测的基础组件。 在CentOS/RHEL系统上…

作者头像 李华