拓扑排序-LeetCode207. 课程表

1、题目描述

https://leetcode-cn.com/problems/course-schedule/

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

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习

  • 输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。
  • 你可以假定输入的先决条件中没有重复的边
  • 1 <= numCourses <= 10^5
输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

2、代码详解

入度表(广度优先遍历) 拓扑排序

  • 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。
  • 换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0。
  • 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

测试用例图如下

numC = 5
prerequisites = [[1, 0], [3, 0], [2, 1], [3, 1], [4, 2], [4, 3]]
s = Solution()
print(s.canFinish(numC, prerequisites))
from typing import List
from collections import deque

class Solution:
    # 思想:该方法的每一步总是输出当前无前趋(即入度为零)的顶点
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        """
        numCourses:课程门数,prerequisites:课程与课程之间的关系
        """
        # 课程的长度
        courselen = len(prerequisites)
        if courselen == 0:
            # 没有课程,当然可以完成课程的学习
            return True

        # 步骤1:统计每个顶点的入度
        # 入度数组,记录了指向它的结点的个数,一开始全部为 0
        in_degrees = [0 for _ in range(numCourses)]
        # 邻接表,使用散列表是为了去重
        adj = [set() for _ in range(numCourses)]

        # 想学习课程 0 ,需要先完成课程 1 ,用一个匹配来表示: [0,1]
        # [0, 1] 表示 1 在先,0 在后, 1 -> 0
        # 注意:邻接表存放的是后继 successor 结点的集合
        for second, first in prerequisites:
            in_degrees[second] += 1
            adj[first].add(second)  # first位置添加其后继second

        print("in_degrees", in_degrees)

        # 步骤2:拓扑排序开始之前,先把所有入度为 0 的结点加入到一个队列中
        # 首先遍历一遍,把所有入度为 0 的结点都加入队列
        queue = deque()
        for i in range(numCourses):
            if in_degrees[i] == 0:
                queue.append(i)

        counter = 0
        while queue:
            top = queue.popleft()
            counter += 1
            # 步骤3:把这个结点的所有后继结点的入度减去 1,如果发现入度为 0 ,就马上添加到队列中
            for successor in adj[top]:  # 遍历top节点所有的后继
                in_degrees[successor] -= 1
                if in_degrees[successor] == 0:
                    queue.append(successor)

        return counter == numCourses

时间复杂度:O(E + V)。这里 E 表示邻边的条数,V 表示结点的个数。初始化入度为 0 的集合需要遍历整张图,具体做法是检查每个结点和每条边,因此复杂度为 O(E+V),然后对该集合进行操作,又需要遍历整张图中的每个结点和每条边,复杂度也为 O(E+V);
空间复杂度:O(E + V):邻接表长度是 V,每个课程里又保存了它所有的边。

https://leetcode-cn.com/problems/course-schedule/solution/tuo-bu-pai-xu-by-liweiwei1419/

https://leetcode-cn.com/problems/course-schedule/solution/course-schedule-tuo-bu-pai-xu-bfsdfsliang-chong-fa/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值