【力扣hot100】207 课程表(c++、python)解析

相关题目: 210 课程表2

【力扣hot100】207 课程表(c++、python)解析

1.官方题解:

这是一题经典的「拓扑排序」问题

给定一个包含 n 个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:对于图 G 中的任意一条有向边 (u,v),u 在排列中都出现在 v 的前面那么称该排列是图 G 的「拓扑排序」

*1.如果图 G 中存在环(即图 G 不是「有向无环图」),那么图 G 不存在拓扑排序。这是因为假设图中存在环 “x1,x2,…,„,í,那么 x1在排列中必须出现在 xn 的前面,但 xn同时也必须出现在 x1的前面,因此不存在一个满足要求的排列,也就不存在拓扑排序

*2.如果图 G 是有向无环图,那么它的拓扑排序可能不止一种。举一个最极端的例子,如果图 G 值包含n个节点却没有任何边,那么任意一种编号的排列都可以作为拓扑排序

有了上述的简单分析,我们就可以将本题建模成一个求拓扑排序的问题了

我们将每一门课看成一个节点
如果想要学习课程 A 之前必须完成课程 B,那么我们从 B 到 A 连接一条有向边。这样以来,在拓扑排序中,B一定出现在 A 的前面。

在这里插入图片描述

在这里插入图片描述

1.1深搜

从出度思考(从后往前排序), 出度为0的节点在拓扑排序中一定排在后面, 然后删除和该节点对应的边, 迭代寻找出度为0的节点

我们可以将深度优先搜索的流程与拓扑排序的求解联系起来,用一个栈来存储所有已经搜索完成的节点。

对于一个节点 u,如果它的所有相邻节点都已经搜索完成,那么在搜索回溯到u的时候,u本身也会变成一个已经搜索完成的节点。这里的「相邻节点」指的是从u出发通过一条有向边可以到达的所有节点。

假设我们当前搜索到了节点u,如果它的所有相邻节点都已经搜索完成,那么这些节点都已经在栈中了,此时我们就可以把u入栈。可以发现,如果我们从栈顶往栈底的顺序看,由于 u 处于栈顶的位置,那么 u 出现在所有 u 的相邻节点的前面。因此对于 u 这个节点而言,它是满足拓扑排序的要求的。

这样以来,我们对图进行一遍深度优先搜索。当每个节点进行回溯的时候,我们把该节点放入栈中。最终从栈顶到栈底的序列就是一种拓扑排序。
在这里插入图片描述
在这里插入图片描述

c++版本

//一个二维向量,用于表示课程之间的依赖关系,每个元素`edges[u]`表示依赖先修课程`u`的课程。
//`visited`:用于标记课程的访问状态,0表示未访问,1表示正在访问,2表示已访问。
//`valid`:表示当前的课程安排是否有效,初始值为`true`
class Solution{
private:
    vector<vector<int>> edges; 
    vector<int> visited;
    bool valid = true;

public:
//用于从课程`u`开始进行深度优先遍历。在遍历过程中,会对课程进行标记,同时检查是否存在环路。若存在环路,则将`valid`设为`false`,表示无法完成所有课程。
    void dfs(int u) 
    {
        visited[u] = 1;//从课程`u`开始进行深度优先遍历
        for(int v:edges[u])//遍历课程u的先修课程
        {
            if(visited[v] == 0)//如果有没访问的先修课程
            {
                dfs(v);//dfs
                if(!valid)
                {
                    return;
                }
            }
            else if(visited[v] ==1)//出现环路
            {
                valid = false;//直接置为false
                return;
            }
        }
        visited[u] =2;//u这个课程 已访问结束
    }
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites)//函数实现
    {
        //根据输入的先修课程信息,构建课程之间的依赖关系
        edges.resize(numCourses);
        visited.resize(numCourses);
        for (const auto& info:prerequisites)
        {
            edges[info[1]].push_back(info[0]);
//举个例子,在 prerequisites 列表中有一个元素 [1, 0],意味着课程 1 的先修课程是课程 0。那么在 edges 字典中,edges[0] 
//就是存储了依赖于课程 0 的所有课程的列表。而在这一行代码中,就将课程 1 的编号添加到了 edges[0] 对应的列表中,表示课程 1 依赖于课程 0
        }
        for(int i = 0; i<numCourses &&valid; ++i)
        {
            if(!visited[i])
            {
                dfs(i);
            }
        }
        return valid;
    }
};

python版本

class Solution:
    #它接受两个参数:`numCourses`表示课程总数,`prerequisites`是一个二维列表,表示课程之间的先修关系。函数返回一个布尔值,表示是否能够完成所有课程
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        edges = collections.defaultdict(list) #导入Python的collections模块,用于创建默认字典使得在后续添加元素时不需要手动创建空列表。
        visited = [0] * numCourses #创建了一个长度为`numCourses`的列表`visited`用于标记课程的访问状态,初始值都为0。
        result = list()#创建一个空列表`result`,用于存储访问过的课程的结果
        valid = True #表示当前的课程安排是否有效,默认为True。

        #将课程之间的依赖关系添加到`edges`中
        for info in prerequisites:
            edges[info[1]].append(info[0])#表示先修课程info[1] 是 现在课程info[0] 的先修课程,把现在的课程添加到先修课程的edges中
    
        def dfs(u:int):
            #在函数内部使用`nonlocal valid`来声明`valid`变量为非局部变量,以便在内部函数中修改外部函数的变量
            nonlocal valid
            visited[u] = 1#首先标记当前课程为已访问状态(1),然后对当前课程的所有依赖课程进行遍历
            for v in edges[u]:
                if visited[v] == 0:
                    dfs(v)#如果依赖课程未被访问过,则递归调用`dfs`函数
                    if not valid:
                        return
                elif visited[v] == 1:# 如果依赖课程正在被访问中(标记为1),表示存在环路,将`valid`设为False,并直接返回
                    valid = False
                    return
            visited[u] = 2
            result.append(u)#遍历完所有依赖课程后,将当前课程标记为已完成状态(2),并将其添加到结果列表中
        for i in range(numCourses):
            if valid and not visited[i]:
                dfs(i)

        return valid    
      

1.2广搜

先学C1, 为0直接加入队列中
在这里插入图片描述
C3和C8都包含C1的入度,-1,C8加入队列中(学完一门课后更新入度,为0加入队列)
在这里插入图片描述
有向有环图无法执行
在这里插入图片描述
在这里插入图片描述
方法一的深度优先搜索是一种「逆向思维」:最先被放入栈中的节点是在拓扑排序中最后面的节点。我们也可以使用正向思维,顺序地生成拓扑排序,这种方法也更加直观。

我们考虑拓扑排序中最前面的节点,该节点一定不会有任何入边,也就是它没有任何的先修课程要求。当我们将一个节点加入答案中后,我们就可以移除它的所有出边,代表着它的相邻节点少了一门先修课程的要求。

如果某个相邻节点变成了「没有任何入边的节点」,那么就代表着这门课可以开始学习了。按照这样的流程,我们不断地将没有入边的节点加入答案,,直到答案中包含所有的节点(得到了一种拓扑排序)或者不存在没有入边的节点(图中包含环)

上面的想法类似于广度优先搜索,因此我们可以将广度优先搜索的流程与拓扑排序的求解联系起来。

c++版本

//广搜
class Solution{
    private:
        vector<vector<int>> edges; //这个列表存储了依赖于当前课程的先修课程的所有课程的编号
        vector<int> indeg; //一个一维向量,用于表示课程的入度(即指向该课程的边的数量)
    public:
        bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
        {
            edges.resize(numCourses);
            indeg.resize(numCourses);
            for(const auto & info:prerequisites)
            {
                //根据输入的先修课程信息,构建课程之间的依赖关系,并更新课程的入度
                edges[info[1]].push_back(info[0]); //这个列表存储了依赖于当前课程的先修课程info[1]的所有课程info[0]的编号
                ++indeg[info[0]];//增加现在课程的入度
            }
            queue<int> q;//队列q
            for(int i = 0;i<numCourses;++i)
            {
                if(indeg[i] == 0)//如果入度为0
                {
                    q.push(i);//直接push到队列中
                }
            }
            int visited = 0;
            while (!q.empty())
            {
                ++visited;
                int u = q.front();
                q.pop();
                for(int v:edges[u])
                {
                    --indeg[v];
                    if(indeg[v] == 0)
                    {
                        q.push(v);
                    }
                }
            }
            return visited == numCourses;
        }
};

相关推荐

  1. 】210 课程表(c++)

    2024-03-27 09:34:05       46 阅读
  2. Python方法

    2024-03-27 09:34:05       42 阅读
  3. hot100 -- 技巧

    2024-03-27 09:34:05       24 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-27 09:34:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-27 09:34:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-27 09:34:05       87 阅读
  4. Python语言-面向对象

    2024-03-27 09:34:05       96 阅读

热门阅读

  1. leetcode 1218.最长定差子序列

    2024-03-27 09:34:05       39 阅读
  2. 了解什么是Docker

    2024-03-27 09:34:05       39 阅读
  3. React常见跳转方式汇总

    2024-03-27 09:34:05       42 阅读
  4. 特征提取技术实例

    2024-03-27 09:34:05       40 阅读
  5. 【计算机网络教程】(第六版)第2章课后习题答案

    2024-03-27 09:34:05       40 阅读
  6. Chrome安装Vue插件vue-devtools

    2024-03-27 09:34:05       49 阅读
  7. STM32 RC522智能门锁

    2024-03-27 09:34:05       42 阅读
  8. ref 解包细节

    2024-03-27 09:34:05       41 阅读
  9. VUE3——Proxy API 与VUE2——defineProperty API区别

    2024-03-27 09:34:05       39 阅读