leetcode-拓扑排序

之前刷了不少leetcode的题目,现在开始春招了,特地把之前写的代码上传了github:https://github.com/czy36mengfei/leetcode,欢迎大家下载、star。

知识拓展: 拓扑排序

拓扑排序并非一种排序算法,它能得到一个 AOV 网络的拓扑序列,用于判断有向无环图中是否有环,即可以判断一系列活动是否有循环依赖;

具体例子:去店里吃饭的问题:顾客要求先吃饭再付钱,商家要求先收钱再做菜,这就是循环依赖,拓扑排序就可以帮助我们判断是否形成环。

步骤:找无前驱的结点(即入度为 0 的结点),一个一个地删去(使用队列或者栈),删的时候,把邻居结点的入度 -1。

“拓扑排序”用于对有先后顺序的任务的输出,如果先后顺序形成一个环,那么就表示这些任务头尾依赖,就永远不能完成。

例题 – LeetCode 第 207 题:课程表

现在你总共有 n 门课需要选,记为 0n-1

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
123

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
123

说明:

  1. 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
  2. 你可以假定输入的先决条件中没有重复的边。

提示:

  1. 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
  2. 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
  3. 拓扑排序也可以通过 BFS 完成。

方法一:拓扑排序(Kahn 算法)

拓扑排序实际上应用的是贪心算法,贪心算法简而言之:每一步最优,全局就最优)。具体到拓扑排序,每一次都输出入度为 0 的结点,并移除它、修改它指向的结点的入度,依次得到的结点序列就是拓扑排序的结点序列。如果图中还有结点没有被移除,则说明“不能完成所有课程的学习”。

拓扑排序保证了每个活动(在这题中是“课程”)的所有前驱活动都排在该活动的前面,并且可以完成所有活动。拓扑排序的结果不唯一。拓扑排序还可以用于检测一个有向图是否有环。相关的概念还有 AOV 网,这里就不展开了。

具体做如下:

1、在开始排序前,扫描对应的存储空间(使用邻接表),将入度为 00 的结点放入队列。

2、只要队列非空,就从队首取出入度为 0 的结点,将这个结点输出到结果集中,并且将这个结点的所有邻接结点(它指向的结点)的入度减 1,在减 1 以后,如果这个被减 1 的结点的入度为 0 ,就继续入队。

3、当队列为空的时候,检查结果集中的顶点个数是否和课程数相等即可。

class Solution(object):# 思想:该方法的每一步总是输出当前无前趋(即入度为零)的顶点def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:""":type numCourses: int 课程门数:type prerequisites: List[List[int]] 课程与课程之间的关系:rtype: bool"""# 课程的长度clen = len(prerequisites)if clen == 0:# 没有课程,当然可以完成课程的学习return True# 步骤1:统计每个顶点的入度# 入度数组,记录了指向它的结点的个数,一开始全部为 0in_degrees = [0 for _ in range(numCourses)]# 邻接表,使用散列表是为了去重adj = [set() for _ in range(numCourses)]# 想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]# [0, 1] 表示 1 在先,0 在后# 注意:邻接表存放的是后继 successor 结点的集合for second, first in prerequisites:in_degrees[second] += 1adj[first].add(second)# 步骤2:拓扑排序开始之前,先把所有入度为 0 的结点加入到一个队列中# 首先遍历一遍,把所有入度为 0 的结点都加入队列queue = []for i in range(numCourses):if in_degrees[i] == 0:queue.append(i)counter = 0while queue:top = queue.pop(0)counter += 1# 步骤3:把这个结点的所有后继结点的入度减去 1,如果发现入度为 0 ,就马上添加到队列中for successor in adj[top]:in_degrees[successor] -= 1if in_degrees[successor] == 0:queue.append(successor)return counter == numCourses

方法二:深度优先遍历

这里要使用逆邻接表。其实就是检测这个有向图中有没有环,只要存在环,这些课程就不能按要求学完。

具体方法是:

第 1 步:构建逆邻接表;

第 2 步:递归处理每一个还没有被访问的结点,具体做法很简单:对于一个结点来说,先输出指向它的所有顶点,再输出自己

第 3 步:如果这个顶点还没有被遍历过,就递归遍历它,把所有指向它的结点都输出了,再输出自己。注意:当访问一个结点的时候,应当先递归访问它的前驱结点,直至前驱结点没有前驱结点为止

class Solution(object):# 这里使用逆邻接表def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:""":type numCourses: int 课程门数:type prerequisites: List[List[int]] 课程与课程之间的关系:rtype: bool"""# 课程的长度clen = len(prerequisites)if clen == 0:# 没有课程,当然可以完成课程的学习return True# 深度优先遍历,判断结点是否访问过# 这里要设置 3 个状态# 0 就对应 False ,表示结点没有访问过# 1 就对应 True ,表示结点已经访问过,在深度优先遍历结束以后才置为 1# 2 表示当前正在遍历的结点,如果在深度优先遍历的过程中,# 有遇到状态为 2 的结点,就表示这个图中存在环visited = [0 for _ in range(numCourses)]# 逆邻接表,存的是每个结点的前驱结点的集合# 想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]# 1 在前,0 在后inverse_adj = [set() for _ in range(numCourses)]for second, first in prerequisites:inverse_adj[second].add(first)for i in range(numCourses):# 在遍历的过程中,如果发现有环,就退出if self.__dfs(i, inverse_adj, visited):return Falsereturn Truedef __dfs(self, vertex, inverse_adj, visited):"""注意:这个递归方法的返回值是返回是否有环:param vertex: 结点的索引:param inverse_adj: 逆邻接表,记录的是当前结点的前驱结点的集合:param visited: 记录了结点是否被访问过,2 表示当前正在 DFS 这个结点:return: 是否有环,返回 True 表示这个有向图有环"""# 2 表示这个结点正在访问if visited[vertex] == 2:# 表示遇到环return Trueif visited[vertex] == 1:return Falsevisited[vertex] = 2for precursor in inverse_adj[vertex]:# 如果有环,就返回 True 表示有环if self.__dfs(precursor, inverse_adj, visited):return True# 1 表示访问结束# 先把 vertex 这个结点的所有前驱结点都输出之后,再输出自己visited[vertex] = 1return False

点我有惊喜

每日一题

课程表 II

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例 1:

输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
说明:

输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
提示:

这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule-ii

leetcode-拓扑排序相关推荐

  1. LeetCode 207. Course Schedule--有向图找环--面试算法题--DFS递归,拓扑排序迭代--Python

    题目地址:Course Schedule - LeetCode There are a total of n courses you have to take, labeled from 0 to n ...

  2. LeetCode Course Schedule II(拓扑排序)

    问题:给出个课程个数,及前提条件对[v,u],即修课程v之前需要修课程u,如果可以输出修的课程顺序 思路: 对于有向图的拓扑排序 一种方式是使用dfs,访问结点的状态分为三种,white,gray,b ...

  3. leetcode 310. Minimum Height Trees | 310. 最小高度树(图的邻接矩阵DFS / 拓扑排序)

    题目 https://leetcode.com/problems/minimum-height-trees/ 题解 方法1:图的邻接矩阵 DFS(超时) 我一想,这不就是个图嘛,于是随手敲出一个 DF ...

  4. LeetCode 2192. 有向无环图中一个节点的所有祖先(拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个正整数 n ,它表示一个 有向无环图 中节点的数目,节点编号为 0 到 n - 1 (包括两者). 给你一个二维整数数组 edges ,其中 ed ...

  5. LeetCode 2050. 并行课程 III(拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个整数 n ,表示有 n 节课,课程编号从 1 到 n . 同时给你一个二维整数数组 relations ,其中 relations[j] = [p ...

  6. LeetCode 1786. 从第一个节点出发到最后一个节点的受限路径数(迪杰斯特拉 + 拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 现有一个加权无向连通图. 给你一个正整数 n ,表示图中有 n 个节点,并按从 1 到 n 给节点编号:另给你一个数组 edges ,其中每个 edges ...

  7. LeetCode 1743. 从相邻元素对还原数组(拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 存在一个由 n 个不同元素组成的整数数组 nums ,但你已经记不清具体内容. 好在你还记得 nums 中的每一对相邻元素. 给你一个二维整数数组 adj ...

  8. LeetCode 1203. 项目管理(两次拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 公司共有 n 个项目和 m 个小组,每个项目要不无人接手,要不就由 m 个小组之一负责. group[i] 表示第 i 个项目所属的小组,如果这个项目目前 ...

  9. LeetCode 802. 找到最终的安全状态(逆向图+拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 在有向图中, 我们从某个节点和每个转向处开始, 沿着图的有向边走. 如果我们到达的节点是终点 (即它没有连出的有向边), 我们停止. 现在, 如果我们最后 ...

  10. LeetCode 851. 喧闹和富有(拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 在一组 N 个人(编号为 0, 1, 2, ..., N-1)中,每个人都有不同数目的钱,以及不同程度的安静(quietness). 为了方便起见,我们将 ...

最新文章

  1. centos7下使用yum安装mysql
  2. 笔记-项目进度管理-资源平衡和资源平滑
  3. 共享一PYTHON 相关应用领域的介绍资料
  4. Linux学习1——文件权限
  5. python count函数用法 comm_python3:MySQL 8.0学习笔记(第五部分:单表查询操作)
  6. linux循环控制结构,Linux Shell 之 Shell 基本控制结构(二)(循环结构)
  7. 使用GDAL对HDF数据进行geoloc校正
  8. 使用 IntelliJ IDEA 导入 Spark源码及编译 Spark 源代码
  9. 这辈子都没有好好的认认真真的过过一天
  10. VS中添加新项 数据选项卡下没有ADO.NET实体数据模型解决方案
  11. C# Parse and TryParse 方法详解
  12. android runnable传递参数,Android – 如何将数据传递到RunOnUiThread中的Runnable?
  13. Win10命令提示符快捷键汇总
  14. Sentaurus 入门之一安装教程
  15. 安装CAD2006出现html,win10系统安装cad2006出现已终止CAd2006-simplifieng安装的设置教程...
  16. 安卓车机数字时间屏保
  17. 华为手机怎样无线与电脑连接服务器,华为手机如何与电脑远程连接服务器
  18. ③电子产品拆解分析-充电宝台灯
  19. 关于NdFeB样品的测量阶段总结
  20. 赤霉素3β-羟化酶的下调增强了大豆的光合作用并提高了种子产量

热门文章

  1. 回到那个夏天(千与千寻)
  2. 数据分析应该怎么做?
  3. python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置...
  4. 费控产品之易快报洞察解析
  5. Focal Loss 简介
  6. 用php照片艺术化,Lab:照片艺术化调色处理介绍
  7. echarts实现全国及各省市地图(内附地图json文件)
  8. 如何恢复已删除的照片
  9. Cadence改背景色
  10. springcloud24:分布式事务 Seata处理分布式事务总结篇