拓扑排序
https://labuladong.gitee.io/algo/2/20/39/
课程表 I
https://leetcode-cn.com/problems/course-schedule/
课和它的前置课程可以视为图的关系,如果出现有环图,则必不能完成课程。
注意,有的课可能也是没又前置课程的,也可能多个课依赖于一个前置课程,因此不能使用是否已经访问过 visited[i]
的值来判断是否走到了重复的环上,而应该在 path[i]
上判断当前路径上是否有环。
DFS 遍历
用函数调用栈来完成 BFS。每访问一个图节点,就继续访问他它的邻接节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| var visited []bool var path []bool
var hasCycle bool
func canFinish(numCourses int, prerequisites [][]int) bool { graph := buildGrapth(numCourses, prerequisites)
visited = make([]bool, numCourses) path = make([]bool, numCourses) hasCycle = false for i := 0; i < numCourses; i++ { traverse(graph, i) }
return !hasCycle }
func traverse(graph [][]int, curr int) { if path[curr] { hasCycle = true } if visited[curr] || hasCycle { return } visited[curr] = true path[curr] = true for _, next := range graph[curr] { traverse(graph, next) } path[curr] = false }
func buildGrapth(numCourses int, prerequisites [][]int) [][]int { graph := make([][]int, numCourses) for i := range graph { graph[i] = make([]int, 0) }
for _, dependency := range prerequisites { from, to := dependency[0], dependency[1] graph[from] = append(graph[from], to) }
return graph }
|
BFS 遍历
BFS 判断图中有无环,需要借助每个节点的入度信息(指向这个节点的边的数目)。
流程:
- 构建邻接表
- 构建
indegree
数组,记录每个节点的入度
- 初始化 BFS 队列,将入度为 0 的节点进队
- 执行 BFS 循环,不断弹出节点并将其邻接节点的入度 -1,将入度变为 0 的节点进队
- 如果所有节点都被遍历过,则说明不存在环
当节点入度为 0 时,我们才去访问它的邻接节点。如果队列空了,而还有节点没访问完,说明这些节点的入度全不为 0,它们肯定存在环的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| func canFinish(numCourses int, prerequisites [][]int) bool { graph := buildGrapth(numCourses, prerequisites)
indegrees := make([]int, numCourses) for _, dependency := range prerequisites { indegrees[dependency[1]] += 1 }
queue := make([]int, 0) for i, indegree := range indegrees { if indegree == 0 { queue = append(queue, i) } }
count := 0 for len(queue) != 0 { sz := len(queue) for i := 0; i < sz; i++ { curr := queue[0] queue = queue[1:]
count += 1
for _, next := range graph[curr] { indegrees[next] -= 1 if indegrees[next] == 0 { queue = append(queue, next) } } } }
return count == numCourses }
func buildGrapth(numCourses int, prerequisites [][]int) [][]int { graph := make([][]int, numCourses) for i := range graph { graph[i] = make([]int, 0) }
for _, dependency := range prerequisites { from, to := dependency[0], dependency[1] graph[from] = append(graph[from], to) }
return graph }
|
课程表 II
https://leetcode-cn.com/problems/course-schedule-ii/
与上一题的区别是,此题不仅要判断能否完成,还需要找出正确的完成顺序。
拓扑排序
简单来说,拓扑排序就是将图的节点拉成一条直线,且所有箭头的方向都是一致的。
如果一幅图有环,则无法进行拓扑排序,否则一定可以进行拓扑排序。
完成拓扑排序,只需要在图节点的后序遍历位置加入自己即可,视建图方式来判断要不要将这个列表反转。
本题中,邻接表 graph[i][j]
定义为要选修 i
,需要先修 j
,因此按照后序遍历,子节点会在列表前面,即前置课程在前面,是正确的顺序。代码与上体几乎一样,先判断有无环,如果没有就输出拓扑顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| var visited []bool var path []bool
var hasCycle bool
var postOrder []int
func findOrder(numCourses int, prerequisites [][]int) []int { visited = make([]bool, numCourses) path = make([]bool, numCourses) hasCycle = false
postOrder = make([]int, 0)
graph := buildGrapth(numCourses, prerequisites) for i := 0; i < numCourses; i++ { traverse(graph, i) }
if hasCycle { return []int{} }
return postOrder }
func traverse(graph [][]int, curr int) { if path[curr] { hasCycle = true } if visited[curr] || hasCycle { return } visited[curr] = true path[curr] = true for _, next := range graph[curr] { traverse(graph, next) } postOrder = append(postOrder, curr) path[curr] = false }
func buildGrapth(numCourses int, prerequisites [][]int) [][]int { graph := make([][]int, numCourses) for i := range graph { graph[i] = make([]int, 0) }
for _, dependency := range prerequisites { from, to := dependency[0], dependency[1] graph[from] = append(graph[from], to) }
return graph }
|
拓扑排序 BFS 版本
容易看出,BFS 判断有无环的算法,直接输出拓扑排序的结果。由于图定义的关系,这里需要反转一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| func findOrder(numCourses int, prerequisites [][]int) []int { graph := buildGrapth(numCourses, prerequisites)
result := make([]int, 0)
indegrees := make([]int, numCourses) for _, dependency := range prerequisites { indegrees[dependency[1]] += 1 }
queue := make([]int, 0) for i, indegree := range indegrees { if indegree == 0 { queue = append(queue, i) } }
count := 0 for len(queue) != 0 { sz := len(queue) for i := 0; i < sz; i++ { curr := queue[0] queue = queue[1:]
count += 1 result = append(result, curr)
for _, next := range graph[curr] { indegrees[next] -= 1 if indegrees[next] == 0 { queue = append(queue, next) } } } }
if count != numCourses { return []int{} } reverse(result) return result }
func buildGrapth(numCourses int, prerequisites [][]int) [][]int { graph := make([][]int, numCourses) for i := range graph { graph[i] = make([]int, 0) }
for _, dependency := range prerequisites { from, to := dependency[0], dependency[1] graph[from] = append(graph[from], to) }
return graph }
func reverse(nums []int) { n := len(nums) for i := 0; i < n / 2; i++ { nums[i], nums[n-1-i] = nums[n-1-i], nums[i] } }
|