• LeetCode 热题 100 | 图论(二)


    目录

    1  基础知识

    1.1  什么是拓扑排序

    1.2  如何进行拓扑排序

    1.3  拓扑排序举例

    2  207. 课程表

    3  210. 课程表 II


    菜鸟做题,语言是 C++

    1  基础知识

    1.1  什么是拓扑排序

    含义:根据节点之间的依赖关系来生成一个有序的序列。

    应用:

    • 在项目管理中,按照任务之间的的依赖关系来安排执行顺序。
    • 在编译原理中,按照编译单元的依赖关系来确定编译单元的生成顺序。
    • 在程序设计中,按照模块之间的依赖关系来确定模块的加载和执行顺序。

    突然想起来好像在操作系统原理里学过!

    1.2  如何进行拓扑排序

    排序步骤:

    1. 选择入度为 0 的节点加入到排序结果中,并从图中移除该节点以及它的所有出边;
    2. 重复步骤 1,每次都选择入度为 0 的节点加入到排序结果中,并更新图中剩余节点的入度;
    3. 重复步骤 1 和 2,直到所有的节点都被加入到排序结果中,或者不再存在入度为 0 的节点。

    名词解释:

    • → 节点  属于节点的 入边
    • 节点 →  属于节点的 出边

    如果图中不再存在入度为 0 的节点,且并非所有的节点都被加入到排序结果中,那么说明原图中存在环。综上,拓扑排序的主要功能是帮助安排顺序,顺带帮助检测图中有没有环。

    1.3  拓扑排序举例

    下图描述了一个拓扑排序过程。

    ① 入度为 0 的节点有 B、D,我们任意选择 B,并删除它的出边:

    ② 入度为 0 的节点有 A、D,我们任意选择 A,并删除它的出边;更新后,入度为 0 的节点有 C、D,我们任意选择 C,并删除它的出边:

    ③ 入度为 0 的节点有 D,我们选择 D,并删除它的出边;更新后,入度为 0 的节点有 F,我们选择 F,并删除它的出边:

    以此类推,不再赘述。通过 “任意选择” 一词可以看出,拓扑排序的结果不止一种。

    2  207. 课程表

    感觉解题方法和拓扑排序关系不大,更多的是从题意出发;想了很久要怎么才能把思路表述清楚,最终认为还是看代码来得快 TT

    解题思路:假设有先修课程对 [0,1] 和 [0,2]

    • 初始化:获取每门课程的先修课程数组,比如:0 对应 [1,2]
    • 初始化:设置记录访问状态的数组,并将访问状态全部置为 0
    • 循环:访问每门课程,判断它的先修课程是否已经被全部修完
    • 如果它的先修课程未被全部修完,则表明无法完成所有课程的学习

    访问状态:

    • visited[course] = 0:该门课程还未被学习
    • visited[course] = 1:该门课程正在被学习
    • visited[course] = 2:该门课程已经被学习

    针对 “该门课程正在被学习” 的说法,需要说明的是,这是算法题而不是现实生活!如果一门课正在被学习,不是代表学习它需要一段时间,而是代表它的先修课程不可能被修完,导致我们永远学不了它。

    1. class Solution {
    2. public:
    3. vector<int> visited;
    4. vectorint>> mustFinish;
    5. bool possible = true;
    6. void helper(int course) {
    7. // 指明该门课程正在被学习
    8. visited[course] = 1;
    9. // 判断其先修课程是否已被修完
    10. for (auto & preCourse : mustFinish[course]) {
    11. // 递归访问未被学习的先修课程
    12. if (visited[preCourse] == 0) {
    13. helper(preCourse);
    14. if (!possible) return;
    15. // 先修课程正在被学习(因为卡住了)
    16. } else if (visited[preCourse] == 1) {
    17. possible = false;
    18. return;
    19. }
    20. }
    21. // 指明该门课程已经被学习
    22. visited[course] = 2;
    23. }
    24. bool canFinish(int numCourses, vectorint>>& prerequisites) {
    25. visited.resize(numCourses);
    26. mustFinish.resize(numCourses);
    27. // 获取每门课程的先修课程数组
    28. for (auto & p : prerequisites) {
    29. mustFinish[p[0]].push_back(p[1]);
    30. }
    31. // 循环访问每门课程
    32. for (int i = 0; i < numCourses && possible; ++i) {
    33. helper(i);
    34. }
    35. return possible;
    36. }
    37. };

    3  210. 课程表 II

    在  207. 课程表  的基础上,增加一个数据结构来存储课程顺序即可。

    唯一区别在于:

    for (int i = 0; i < numCourses && possible && !visited[i]; ++i)

    新增条件 !visited[i],不允许已经被访问过的课程再被访问,避免了课程重复被学习。

    1. class Solution {
    2. public:
    3. vector<int> visited;
    4. vectorint>> mustFinish;
    5. bool possible = true;
    6. vector<int> ans;
    7. void helper(int course) {
    8. visited[course] = 1;
    9. for (auto & preCourse : mustFinish[course]) {
    10. if (visited[preCourse] == 0) {
    11. helper(preCourse);
    12. if (!possible) return;
    13. } else if (visited[preCourse] == 1) {
    14. possible = false;
    15. return;
    16. }
    17. }
    18. visited[course] = 2;
    19. ans.push_back(course);
    20. }
    21. vector<int> findOrder(int numCourses, vectorint>>& prerequisites) {
    22. visited.resize(numCourses);
    23. mustFinish.resize(numCourses);
    24. for (auto & p : prerequisites) {
    25. mustFinish[p[0]].push_back(p[1]);
    26. }
    27. for (int i = 0; i < numCourses && possible && !visited[i]; ++i) {
    28. helper(i);
    29. }
    30. if (possible) return ans;
    31. return {};
    32. }
    33. };

  • 相关阅读:
    Docker安装
    Windows11 家庭版开启远程桌面解决方案之RDP Wrapper Library,小白全面攻略
    面试不面试,你都必须得掌握的vue知识
    网络安全笔记7——防火墙技术
    易优cms小程序常见问题
    CDN是什么?(网络零基础入门篇)
    elasticsearch 安装
    安卓APP源码和设计报告——快递查询录入系统
    记一次 Java Testcontainers CPU 100% 问题排查过程
    @AutoWired与@Resource
  • 原文地址:https://blog.csdn.net/m0_64140451/article/details/136356334