3、环检测和拓扑排序
3、环检测和拓扑排序
图这种数据结构有一些比较特殊的算法,比如二分图判断,有环图无环图的判断,拓扑排序等。
而对于拓扑排序首先就是要判断图中是不是有环,如果有环就没有必要进行排序了。
而什么叫做拓扑排序呢就是
直观地说就是,让你把一幅图「拉平」,而且这个「拉平」的图里面,所有箭头方向都是一致的,比如上图所有箭头都是朝右的。
如果一幅有向图中存在环,是无法进行拓扑排序的,因为肯定做不到所有箭头方向一致;反过来,如果一幅图是「有向无环图」,那么一定可以进行拓扑排序。
一、环检测——207. 课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
思路:
这里题目求得是课程之间是否存在相互依赖的关系,如果存在相互依赖关系,那么课程之间就不可能学习完成,如果不存在相互依赖那么就可以学习完成全部的课程。
因为求得是相互依赖的关系所以我们可以使用有向图来表示,求是不是相互依赖,这里指的是所有的节点是否存在相互依赖的关系,那么我们就可以转化为求图中是否存在环。
题目中给的是一个图的边,
第一步我们需要将边的信息转化为图的信息。这里需要注意我们是使用依赖的关系还是被依赖的关系,比如[1,0]我们需要转化为[[], [0]]这种关系
第二步我们需要检测图中每个节点是否存在互相依赖的关系
class Solution:
def __init__(self):
self.flag = False
self.visited = None
self.on_path = None
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 创建邻接表
graph = self.build_graph(numCourses, prerequisites)
print(graph)
# 判断节点之间是否存在互相依赖,这里需要注意需要遍历所有的节点
self.visited = [False] * numCourses
self.on_path = [False] * numCourses
# 注意图中并不是所有节点都相连,所以要用一个for循环将所有节点都作为起点调用一次 DFS 搜索算法。
for i in range(numCourses):
self.dfs(graph, i)
return not self.flag
def dfs(self, graph, node):
if self.on_path[node]:
self.flag = True
if self.visited[node] or self.flag:
return
self.visited[node] = True
self.on_path[node] = True
for val in graph[node]:
self.dfs(graph, val)
self.on_path[node] = False
def build_graph(self, numCourses, prerequisites):
graph = [[] for _ in range(numCourses)]
for edge in prerequisites:
from_, to_ = edge
graph[from_].append(to_)
return graph
210. 课程表 II——拓扑排序
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
- 例如,想要学习课程
0,你需要先完成课程1,我们用一个匹配来表示:[0,1]。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]
提示:
1 <= numCourses <= 20000 <= prerequisites.length <= numCourses * (numCourses - 1)prerequisites[i].length == 20 <= ai, bi < numCoursesai != bi- 所有
[ai, bi]互不相同
这里课程表的第一题有一点不同的就是这里不仅仅要得到是否可以学完全部的课程还要得到课程的排序
class Solution:
def __init__(self):
visited = None
on_path = None
self.flag = False# 这里的True就是有环,不可以学完全部的课程
self.result = []
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# 构建图
graph = self.build_graph(numCourses, prerequisites)
self.visited = [False] * numCourses
self.on_path = [False] * numCourses
# 环检测加收集信息
for i in range(numCourses):
self.dfs(graph, i)
# print("result::", self.result, self.flag)
if self.flag is True:
return []
return self.result[::-1]
def dfs(self, graph, node):
if self.on_path[node]:
self.flag = True
if self.visited[node] or self.flag:
return
self.on_path[node] = True
self.visited[node] = True
for val in graph[node]:
self.dfs(graph, val)
self.result.append(node)
self.on_path[node] = False
def build_graph(self, numCourses, prerequisites):
graph = [[] for _ in range(numCourses)]
for temp in prerequisites:
to_, from_ = temp
graph[from_].append(to_)
return graph