拓扑排序之课程表问题

1. 题目描述

你这个学期必须选修 numCourses 门课程,记为0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组prerequisites 给出,其中prerequisites[i] = [ai, bi] ,表示如果要学习课程ai 则 必须 先学习课程bi

  • 例如,先修课程对[0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1
    请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

2.解题思路

2.1 广度优先 根据入度拓补排序

  1. 统计课程安排图中每个节点的入度,生成 入度表 indegrees
  2. 借助一个队列 queue,将所有入度为 0 的节点入队。
  3. 当 queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre:
  • 并不是真正从邻接表中删除此节点 pre,而是将此节点对应所有邻接节点 cur 的入度 -1,即 indegrees[cur] -= 1
  • 当入度 -1后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点已经被 “删除”,此时将 cur 入队。
  1. 在每次 pre 出队时,执行 numCourses--
  • 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0
  • 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

时间复杂度 O(N + M)

空间复杂度 O(N + M)

2.2 深度优先 判断图中是否有环

1.借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:

  • 未被 DFS 访问:i == 0;
  • 已被其他节点启动的 DFS 访问:i == -1;
  • 已被当前节点启动的 DFS 访问:i == 1。

2.对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。

3.当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1 并返回 True。
若整个图 DFS 结束并未发现环,返回 True。

3. 代码实现

一般这种问题上来先生成邻连表, 后续问题处理起来比较方便

// 1. 广度优先搜索 入度表
func canFinish(numCourses int, prerequisites [][]int) bool {
	// 邻连表
	adjacency := make([][]int, numCourses)
	// 入度表
	indegrees := make([]int, numCourses)
	for _, prerequisite := range prerequisites {
		indegrees[prerequisite[1]]++
		adjacency[prerequisite[1]] = append(adjacency[prerequisite[1]], prerequisite[0])
	}
	queue := []int{}
	for i := 0; i < numCourses; i++ {
		if indegrees[i] == 0 {
			queue = append(queue, i)
		}
	}
	for len(queue) > 0 {
		node := queue[0] // 出队
		queue = queue[1:]
		numCourses--
		// 将队头节点相连的节点的入度-1
		for i := 0; i < len(adjacency[node]); i++ {
			indegrees[adjacency[node][i]]--
			// 入度为0加入队列
			if indegrees[adjacency[node][i]] == 0 {
				queue = append(queue, adjacency[node][i])
			}
		}
	}
	return numCourses == 0
}

// 2. 深度优先搜索 判断图中是否有环
func canFinish(numCourses int, prerequisites [][]int) bool {
	adjacency := make([][]int, numCourses)
	flag := make([]int, numCourses)
	for _, prerequisite := range prerequisites {
		adjacency[prerequisite[1]] = append(adjacency[prerequisite[1]], prerequisite[0])
	}
	for i := 0; i < numCourses; i++ {
		if !dfs(adjacency, flag, i) {
			return false
		}
	}
	return true
}
func dfs(adjacency [][]int, flag []int, i int) bool {
	if flag[i] == -1 {
		return true
	}
	if flag[i] == 1 {
		return false
	}
	flag[i] = 1
	for j := 0; j < len(adjacency[i]); j++ {
		if !dfs(adjacency, flag, adjacency[i][j]) {
			return false
		}
	}
	flag[i] = -1
	return true
}

leetcode 207. 课程表

参考链接

原文地址:https://www.cnblogs.com/lsyy2020/p/14637980.html