一 课程表
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
这道题需要注意的点:
做题过程的纰漏:
注意,python的列表的删除元素,可以通过pop()和remove(),pop只能删除掉对应的序号,remove可以直接删除掉对应的元素。
在遍历列表和字典的时候,删除了里面的元素,应该如何遍历,直接for i in …是不可行的。
字典:更改为列表的形式遍历:
for i in list(a_dict.keys()):
if a_dict[i] == 1:
del a_dict[i]
列表在遍历过程中是可以改变的,直接remove,需要注意的是,如果是遍历列表的序号,则可能出现问题:
for i in range(len(a_list)):
if a_list[i] == 1:
a_list.pop(i)
这个for 后面的range里的范围是固定的,没有修改。所以运行就会超过范围。
1.这道题其实就是判断是否有环,如果出现环,说明两门课无法实现,第一个数字,需要学习的课程数其实也是可有可无,不过可以不用自己对后面的课程求这个数目,简化了。
2.采用深度优先遍历的想法就是从一个结点遍历到尾,看看中间是否出现环,可以设置几个状态,比如没有遍历到的是0,本链正在遍历是1,已经遍历并确认无环的是2.遇到1就说明有环了,遇到2直接return。
3.课程的代号其实就是0-numCourse-1
4.广度优先遍历无法像深度那样检测环,怎么做呢?就是检测入度,依次把入度为0,也就是可以直接学习的课程一一删除,直到删除完入度为0的节点,如果还有节点存在,那么说明这些节点的入度都是大于0,所以无法学习。也就是有环。
1.深度优先遍历
#把所有节点直接赋予一个状态,未搜索-搜索中-已完成
# 如果搜索到搜索中的节点,说明是环,如果是已完成,则可以不用再继续考虑,未搜索则继续
class Solution(object):
def canFinish(self, numCourses, prerequisites):
course_dict = {}
color_dict = {}
# 虽然可以通过prerequisites来得到课程的数目,但是numCourses直接给出了
for i in range(numCourses):
color_dict[i] = 0 # 分为012三种状态
for i in prerequisites:
if i[0] not in course_dict:
course_dict[i[0]] = i[1::] # 往回溯源
else:
course_dict[i[0]] = course_dict[i[0]] + i[1::]
# 这里会出现一个问题!如果[[1,0],[1,2],[0,1]], 说明要2和0才有1
def dfs(node, color_dict):
if color_dict[node] == 2:
return True
elif color_dict[node] == 1:
return False
else:
if node not in course_dict:
color_dict[node] = 2
return True
for i in course_dict[node]:
color_dict[node] = 1
result = dfs(i, color_dict)
if not result:
return False
color_dict[node] = 2
return True
for node in course_dict:
if not dfs(node, color_dict):
return False
return True
2.广度优先遍历
广度优先遍历自然用队列来解决
注意,python的列表的删除元素,可以通过pop()和remove(),pop只能删除掉对应的序号,remove可以直接删除掉对应的元素。
from collections import deque
# 广度优先遍历
# 注意哈,肯定存在没有入度的节点,不然就妥妥无法进行了。相当于如果所有入度为0的节点都去除后,还剩下节点,那么就是闭环的。 度数为0的先入队,提前解决。
class Solution(object):
def canFinish(self, numCourses, prerequisites):
queue = deque()
course_dict = {}
for i in prerequisites:
if i[0] not in course_dict:
course_dict[i[0]] = i[1::]
else:
course_dict[i[0]] += i[1::]
for i in range(numCourses):
if i not in course_dict:
queue.append(i)
if queue == None and numCourses > 0:
return False
while len(queue) > 0:
head = queue.popleft()
for i in list(course_dict.keys()):
# if course_dict[i] == []:
# continue
for course in course_dict[i]:
if course == head:
course_dict[i].remove(course)
# pop只能去掉对应的序号的元素,remove可以直接去掉想去掉的元素
# course_dict[i].pop(courseid)
if course_dict[i] == []: ##### None和[]是不一样的。。。。这里字典变了!!!
queue.append(i)
del course_dict[i]
if course_dict != {}:
return False
return True
拓扑排序
补充一下拓扑排序的概念:
拓扑排序是一个有效的任务顺序,每一门课对应有向图的一个顶点, 先修关系对应有向图的一条边。
相当于拓扑排序就是一种排序,箭头的指向就是任务的顺序,也没有什么特殊的函数。
二 课程表II
区别在于此时要返回拓扑顺序
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
跟上述也是一样的思路,只是多了一个数组来记录顺序而已。
下面举例广度优先,代码基本一样。深度优先也是,尽管可能走一次以上深度,但是不影响,只要是能走,顺序都可以。
from collections import deque
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# 广度优先遍历 通过入度来判断
# 下面course_dict的记法是键对应结点,值是结点的入度。
# 同样,记为出度也是可以,并且会更加简单,就会少去后面每一个节点还要遍历入度的麻烦。
# 注意本题默认[左边就是要学的,右边就是需要先学的]并且只有两个。
color_dict = 0
#for i in range(numCourses):
course_dict = {}
for i in range(len(prerequisites)):
if prerequisites[i][0] not in course_dict:
course_dict[prerequisites[i][0]] = prerequisites[i][1::]
else:
course_dict[prerequisites[i][0]] += prerequisites[i][1::]
sequence = []
queue = deque()
for i in range(numCourses):
if i not in course_dict:
sequence.append(i)
queue.append(i)
while len(queue) > 0:
course = queue.popleft()
for i in list(course_dict.keys()): # 注意括号 course_dict.keys()
if course in course_dict[i]:
course_dict[i].remove(course)
if course_dict[i] == []:
del course_dict[i]
queue.append(i)
sequence.append(i)
if course_dict == {}:
return sequence
else:
return []