图-课程表-深度和广度优先遍历

一 课程表

你这个学期必须选修 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 []




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值