课程表( 拓扑排序/dfs 判环)
题目链接:https://leetcode-cn.com/problems/course-schedule/
题目大意:给定一个课程依赖关系图,比如课程A依赖课程B,课程B依赖课程C,按照上述的依赖关系,能否学习完所有的课程?
先学C,再学B,最后学A即可
方式1:拓扑排序
我们按照图论的思想,将每个课程看做一个节点,将课程间的这种依赖和被依赖的关系看做节点的出度和入度,即
依赖:出度
被依赖:入度
样例如下
A依赖B等价于A节点指向B节点,那么A节点的出度是1,B节点的入度是1
A依赖D等价于A节点指向B节点,那么A节点现在的出度是2,D节点的入度是1
我们首先找到所有入度为0的节点,即这些节点不被任何节点依赖,我们可以先完成这些课程nums1,这些课程被完成后,依赖这些课程的节点nums2其入度也需要被修改,具体的修改操作为nums2节点的入度都需要减去1,因为nums1节点课程都完成了
然后我们继续选取入度为0的点,直到没有入度为0的点
对于这些入度为0的点的存储我们可以采用队列,先进先出
当队列中没有元素后,即所有入度为0的点都被处理了,如果处理的点总数等于节点总数,那么证明可以存在一个拓扑顺序,学习完所有课程,如果不等于,那么证明图中存在环,互相依赖死循环了,就不能学习完所有课程
这种方法叫做拓扑排序,出入队列元素的先后顺序就是拓扑排序的顺序
时间复杂度:O(n+m)
- n 为课程数,m 为先修课程的要求数。这其实就是对图进行广度优先搜索的时间复杂度。
空间复杂度:O(n+m)
- 双层map存储图需要O(m),队列存储需要O(n)
func canFinish(numCourses int, prerequisites [][]int) bool {
G:=make(map[int]map[int]int)
indegree:=make(map[int]int)
var queue []int
// 构建图和入度表
for i:=0;i
方式2:dfs
拓扑排序的方法是基于入度为0的点考虑的,其实我们也可以基于出度为0的点考虑
出度为0意味着此节点不依赖其他节点,只可能被其他节点依赖
那如何寻找到出度为0的节点呢?
答案就是深度优先搜索,dfs
有路径就一直搜索下去,深度优先
对于每个节点u,我们可以定义0,1,2三种状态
-
0 未搜索
- 代表此节点还没有被搜索过
-
1 搜素中
- 我们搜索过这个节点,但还没有回溯到该节点即该节点还没有入栈,还有相邻的节点没有搜索完成
-
2 搜素已完成
- 我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。
我们搜素一个节点x时,会搜索x指向的所有节点
- 如果这些节点没有被搜索过,那么搜索它
- 如果这些节点处于搜索中,也就是被其他dfs搜索过了,那么证明此图中存在环!
- 如果此节点处于搜索已完成,那么跳过
时间复杂度:O(n+m), n是课程数量,m是依赖数量
空间复杂度:O(n+m) 存储图需要O(m),递归需要O(n)
var G map[int]map[int]int
var flag [100005]int
var N int
var result bool
func dfs(index int){
// index标记为已搜索,但是还在搜索和index有关的其他节点
flag[index]=1
for i:=0;i