【LeetCode】课程表(图论判环 拓扑排序/dfs)

课程表( 拓扑排序/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

你可能感兴趣的:(队列,算法,数据结构,leetcode,java)