【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<len(prerequisites);i++{
		v1:=prerequisites[i][0]
		v2:=prerequisites[i][1]
		if _,ok:=G[v1];!ok{
			G[v1]=make(map[int]int)
		}
		G[v1][v2]=1
		indegree[v2]++
	}

	// 入度为0的点加入队列
	for i:=0;i<numCourses;i++{
		if indegree[i]==0{
			queue=append(queue,i)
		}
	}

	c:=0
	for len(queue)!=0{
		temp:=queue[0]
		queue=queue[1:]

		// 符合拓扑排序的节点数量
		c++

		// temp 和 i 存在边,则i的入度减1,如果i入度为0,则加入队列
		for i:=0;i<numCourses;i++{
			if G[temp][i]==1{
				indegree[i]--
				if indegree[i]==0{
					queue=append(queue,i)
				}
			}
		}

	}

	// 符合拓扑排序节点的数量等于节点总数量,证明不存在环,符合要求
	if c==numCourses{
		return true
	}
	return false

}

方式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<N;i++{
		if G[index][i]==1{
			if flag[i]==0{
				// i没有被搜索过,搜索i
				dfs(i)
				if result==false{
					return
				}
			}else if flag[i]==1{
				// i已经被其他dfs标记过了,证明存在环,没有合法拓扑排序
				result=false
				return
			}else if flag[i]==2 {
				continue
				// i 此时已经在拓扑排序中,无需做任何操作
			}
		}
	}

	// index标记为加入到了拓扑排序中
	flag[index]=2
}
func canFinish(numCourses int, prerequisites [][]int) bool {
	G = make(map[int]map[int]int)
	flag=[100005]int{}
	N=numCourses
	result=true

	// 构建图
	for i:=0;i<len(prerequisites);i++{
		v1:=prerequisites[i][0]
		v2:=prerequisites[i][1]
		if _,ok:=G[v1];!ok{
			G[v1]=make(map[int]int)
		}
		G[v1][v2]=1
	}


	// 对每一个没有被搜索过的节点都进行一次搜索
	for i:=0;i<numCourses;i++{
		if flag[i]==0{
			dfs(i)
		}
	}

	return result
}
posted @   西*风  阅读(160)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
历史上的今天:
2018-04-07 HDU1047(多个大数相加)
2018-04-07 HDU1042 N!(大数问题,万进制)
点击右上角即可分享
微信分享提示