• 【力客热题HOT100】-【061】207 课程表


    重点:

    (1)抽象问题:检测一个有向图中是否有环,这个图可能有多个子图;

    (2)检测图中是否有环:深搜拓扑排序(DFS实现)

    (3)拓扑排序最优复杂度为O(m+n),一般使用DFS和BFS两种实现拓扑排序;

    (4)注意深搜和广搜的差别,一个从后往前,一个从前往后

    207. 课程表

    难度中等

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

    在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

    • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

    请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

    示例 1:

    输入:numCourses = 2, prerequisites = [[1,0]]
    输出:true
    解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

    示例 2:

    输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
    输出:false
    解释:总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
    

    提示:

    • 1 <= numCourses <= 105
    • 0 <= prerequisites.length <= 5000
    • prerequisites[i].length == 2
    • 0 <= ai, bi < numCourses
    • prerequisites[i] 中的所有课程对 互不相同

    解析:

    不难想到,本题考查有向图中是否存在环。一般用两种方法解决,深搜和拓扑排序,本文重点讲拓扑排序

    给定一个包含n个节点得有向图G,我们给出其节点编号的一种排列,如果满足:

    对于图中任意一条有向边(u, v),u在排列里并且u都出现在v的前面。

    那么称排列是图G的拓扑排序。根据上述定义,我们得出两个结论:

           ——如果G中存在环,那么图G不存在拓扑排序;

           ——如果G是有向无环图,那么其拓扑排序可能不止一种;

    有了上述知识,我们可以将此题简单抽象成一个拓扑排序问题。

           ——将每门课看成一个节点;

           ——学习A之前学B,那么便存在有向边(B,A);

    理论上来讲,拓扑排序的最最优复杂度为O(m+n),但不可能存在一种该复杂度算法找到拓扑排序。我们一般使用深搜和广搜两种算法实现拓扑排序

    1、广搜优先搜索

    (1)思路:正向思维,先把入度为0的节点加入答案队列,该节点一定不会有任何入边。当我们把一个新节点加入队列后,移除其所有出边。代表与其相邻的节点少了一门先修课。重复操作,直至所有节点加入队列,说明存在拓扑排序,反之就不存在。

    (2)实现

    所需数据结构:队列queue,

    建立哈希表存储答案节点,将所有入度为0的节点存入哈希表map,同时加入队列queue。

            ——依次取队列首部节点,消去边集中的有向边;

            ——遍历新边集,将新的入度为0的节点入队列;

            ——队列为空时说明结束,此时检查哈希表中节点个数或者边集个数。也可提前检查队列元素个数加哈希表元素个数之和说明结束。

    2、深度优先搜索

    (1)每次挑选一个未搜索的节点进行深搜

    (2)对于每个进行深搜的节点,顺着路径往下,标记走过的路,发现本条路某个节点重复出现,说明存在环;

    注意:此时是从后往前找,找的是每个课程的先修课,所以visit数组可以公用一个,以及为什么最后需要逆序。

    代码:

    1. //广度优先搜索
    2. class Solution {
    3. public:
    4. vector<int> findOrder(int numCourses, vectorint>>& prerequisites) {
    5. vector<int> res;
    6. queue<int> que;
    7. vector<int> indeg(numCourses,0);
    8. for(int i=0;isize();i++)
    9. indeg[prerequisites[i][0]]+=1;
    10. for(int i=0;i
    11. if(indeg[i]==0)
    12. que.push(i);
    13. //队列为空结束
    14. while(!que.empty()){
    15. int tmp=que.front();
    16. que.pop();
    17. res.push_back(tmp);
    18. for(int i=0;isize();i++){
    19. if(prerequisites[i][1]==tmp){
    20. indeg[prerequisites[i][0]]-=1;
    21. if(indeg[prerequisites[i][0]]==0)
    22. que.push(prerequisites[i][0]);
    23. }
    24. }
    25. }
    26. if(res.size()==numCourses)
    27. return res;
    28. else
    29. return {};
    30. }
    31. };
    1. class Solution {
    2. private:
    3. // 存储有向图
    4. vectorint>> edges;
    5. // 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
    6. vector<int> visited;
    7. // 用数组来模拟栈,下标 0 为栈底,n-1 为栈顶
    8. vector<int> result;
    9. // 判断有向图中是否有环
    10. bool valid = true;
    11. public:
    12. void dfs(int u) {
    13. // 将节点标记为「搜索中」
    14. visited[u] = 1;
    15. // 搜索其相邻节点
    16. // 只要发现有环,立刻停止搜索
    17. for (int v: edges[u]) {
    18. // 如果「未搜索」那么搜索相邻节点
    19. if (visited[v] == 0) {
    20. dfs(v);
    21. if (!valid) {
    22. return;
    23. }
    24. }
    25. // 如果「搜索中」说明找到了环
    26. else if (visited[v] == 1) {
    27. valid = false;
    28. return;
    29. }
    30. }
    31. // 将节点标记为「已完成」
    32. visited[u] = 2;
    33. // 将节点入栈
    34. result.push_back(u);
    35. }
    36. vector<int> findOrder(int numCourses, vectorint>>& prerequisites) {
    37. edges.resize(numCourses);
    38. visited.resize(numCourses);
    39. for (const auto& info: prerequisites) {
    40. edges[info[1]].push_back(info[0]);
    41. }
    42. // 每次挑选一个「未搜索」的节点,开始进行深度优先搜索
    43. for (int i = 0; i < numCourses && valid; ++i) {
    44. if (!visited[i]) {
    45. dfs(i);
    46. }
    47. }
    48. if (!valid) {
    49. return {};
    50. }
    51. // 如果没有环,那么就有拓扑排序
    52. // 注意下标 0 为栈底,因此需要将数组反序输出
    53. reverse(result.begin(), result.end());
    54. return result;
    55. }
    56. };
  • 相关阅读:
    如何阅读一份源代码?
    【无标题】
    【Java基础】构造方法概述及注意事项
    使用Java实现一个简单的贪吃蛇小游戏
    CloudCompare 二次开发(12)——点云投影到球面
    【Linux网络】ssh服务与配置,实现安全的密钥对免密登录
    QT--气泡框的实现
    利用js写函数返回js基本函数代码
    值得推荐的小型 C 语言开源项目:Triggerhappy
    听书项目总结
  • 原文地址:https://blog.csdn.net/zhuge2017302307/article/details/125928079