【拓扑排序】课程表系列

news2024/10/7 11:24:41

文章目录

  • 课程表(环检测算法)
    • 1. DFS
    • 2. BFS
  • 课程表 II(拓扑序列)
    • 1. DFS
    • 2. BFS
  • 课程表 IV(记忆化搜索)
    • 1. DFS
    • 2. BFS

课程表(环检测算法)

在这里插入图片描述

1. DFS

先修课程之间的关系可以用有向图表示,用哈希表存储邻接表,很显然,当有向图成环时无法完成所有的课程

在这里插入图片描述
环检测算法需要使用两个数组,分别是 visited 和 path,visited数组用来记录遍历过的节点,而path用于记录正在遍历的路径且没有回溯到的节点。如果遍历某条支路时又到达某path数组记录为true的节点,说明成环了

比如上图中,根据dfs,从0出发后会遍历到1、3,然后会遍历2(visited[2]=true、path[2]=true),遍历3(visited[3]=true、path[3]=true),遍历4(visited[4]=true、path[4]=true),遍历2,然而此时发现path[2]=true,出现环!

下图描述了遍历的过程,在 visited 中被标记为 true 的节点用灰色表示,在 path 中被标记为 true 的节点用绿色表示

在这里插入图片描述
图源

class Solution {
public:
    unordered_map<int, vector<int>> mp;
    vector<bool> visited;
    vector<bool> path;
    bool valid;

    void dfs(int start){
        if(path[start]){
            valid = false;
            return;
        }
        if(visited[start]) return;
        visited[start] = true;
        path[start] = true;
        for(int end : mp[start]){
        	// 判断条件不能是:valid && !visited[end],因为要检测环
            if(valid) dfs(end);
        }
        path[start] = false;
    }

    bool canFinish(int n, vector<vector<int>>& prerequisites) {
        visited.resize(n, false);
        path.resize(n, false);
        valid = true;
        for(auto end_start : prerequisites){
            mp[end_start[1]].push_back(end_start[0]);
        }
        // 由于图不像树一样有根节点,图没有入口节点,需要使用for循环试探每一个节点
        for(int i = 0; i < n; i++){
            if(valid) dfs(i);
        }
        return valid;
    }
};

2. BFS

BFS 算法的思路:

  1. 构建邻接表,数据结构为哈希表 + 数组,unordered_map<int, vector<int>>

  2. 构建一个 indegree 数组记录每个节点的入度,即 indegree[i] 记录节点 i 的入度

  3. 对 BFS 队列进行初始化,入度为0说明没有先修课程,将入度为 0 的节点首先入队

  4. 开始执行 BFS ,不断弹出队列中的节点(表示修完一门课),减少相邻节点的入度(表示先修课程 - 1),并将入度变为 0 的节点加入队列

  5. 如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环。

class Solution {
public:
    bool canFinish(int n, vector<vector<int>>& prerequisites) {
        unordered_map<int, vector<int>> mp;
        vector<int> indegree(n, 0);
        for(auto end_start : prerequisites){
            indegree[end_start[0]]++;
            mp[end_start[1]].push_back(end_start[0]);
        }
        queue<int> q;
        // 入度为0,说明不需要依赖其他课程,优先遍历
        // 先将入度为0的节点放入队列
        for(int i = 0; i < n; i++){
            if(indegree[i] == 0){
                q.push(i);
            }
        }
        vector<int> res;
        while(!q.empty()){
            int start = q.front();
            q.pop();
            res.push_back(start); // 出队顺序就是拓扑序
            for(int end : mp[start]){
                // 由于start课程已经修了,将end课程的入度 - 1,表示依赖的课程少一个
                indegree[end]--;
                if(indegree[end] == 0){
                    // 入度为0,说明end课程的先修课程全部学完,可以放入队列遍历
                    q.push(end);
                }
            }
        }
        return res.size() == n;       
    }
};

课程表 II(拓扑序列)

在这里插入图片描述

1. DFS

在这里插入图片描述

后序遍历反转就得到结果

class Solution {
public:
    // 出现环,返回空数组,否则返回拓扑序
    unordered_map<int, vector<int>> mp;
    vector<bool> visited;
    vector<bool> path;
    vector<int> postorder;
    bool valid;

    void dfs(int start){
        if(path[start]){
            valid = false;
            return;
        }
        if(visited[start]) return;
        
        visited[start] = true;
        path[start] = true;
        for(int end : mp[start]){
            if(valid) dfs(end);
        }
        path[start] = false;
        
        postorder.push_back(start);
    }

    vector<int> findOrder(int n, vector<vector<int>>& prerequisites) {
        visited.resize(n, false);
        path.resize(n, false);
        valid = true;
        for(auto end_start : prerequisites){
            mp[end_start[1]].push_back(end_start[0]);
        }
        for(int i = 0; i < n; i++){
            if(valid) dfs(i);
        }
        if(!valid) return {};
        reverse(postorder.begin(), postorder.end());
        return postorder;
    }
};

2. BFS

首先使用indegree数组记录每个节点的入度,将入度为0(表示没有先修课程)的节点放入队列,开始BFS

从队列中取出节点加入数组ans(拓扑序),将该节点能直达的节点end对应的indegree[end]减1,表示end的先修课程少了一门,如果indegree[end]为0,表示end对应的先修课程全部学完,可以加入队列遍历

遍历完后ans的长度若为n,则可以修完所有课程,否则有向图存在环,无法修完所有课程

class Solution {
public:
    vector<int> findOrder(int n, vector<vector<int>>& prerequisites) {
        unordered_map<int, vector<int>> mp;
        vector<int> indegree(n, 0);
        for(auto end_start : prerequisites){
            indegree[end_start[0]]++;
            mp[end_start[1]].push_back(end_start[0]);
        }
        queue<int> q;
        // 入度为0,说明不需要依赖其他课程,优先遍历
        // 先将入度为0的节点放入队列
        for(int i = 0; i < n; i++){
            if(indegree[i] == 0){
                q.push(i);
            }
        }
        vector<int> ans;
        while(!q.empty()){
            int start = q.front();
            q.pop();
            ans.push_back(start); // 出队顺序就是拓扑序
            for(int end : mp[start]){
                // 由于start课程已经修了,将end课程的入度 - 1,表示依赖的课程少一个
                indegree[end]--;
                if(indegree[end] == 0){
                    // 入度为0,说明end课程的先修课程全部学完,可以放入队列遍历
                    q.push(end);
                }
            }
        }
        if(ans.size() != n) return {};
        return ans;        
    }
};

课程表 IV(记忆化搜索)

在这里插入图片描述
在这里插入图片描述

1. DFS

使用一个二维数组isReach表示start和end之间是否可达,其中1表示可达、-1表示不可达、0表示未定

搜索结果保存在二维数组isReach中,下一次搜索可以利用isReach数组

class Solution {
public:
    unordered_map<int, vector<int>> mp;
    vector<vector<int>> isReach; // 1表示可达、-1表示不可达、0表示未定

    bool dfs(int start, int end){
        if(isReach[start][end] == 1) return true;
        if(isReach[start][end] == -1) return false;
        for(int mid : mp[start]){
            if(dfs(mid, end)){
                isReach[start][end] = 1;
                return true;
            }
        }
        isReach[start][end] = -1;
        return false;
    }

    vector<bool> checkIfPrerequisite(int n, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {
        isReach.resize(n, vector<int>(n, 0));
        for(auto start_end : prerequisites){
            mp[start_end[0]].push_back(start_end[1]);
            isReach[start_end[0]][start_end[1]] = 1;
        }
        for(int i = 0; i < n; i++){
            isReach[i][i] = 1;
        }
        vector<bool> ans;
        for(auto q : queries){
            ans.push_back(dfs(q[0], q[1]));
        }
        return ans;
    }
};

2. BFS

每个节点都作为源点,把源点src加入队列中开始BFS,遍历到的所有节点mid,都将isReach[src][mid]都置为true

class Solution {
public:
    unordered_map<int, vector<int>> mp;
    vector<vector<int>> isReach;

    bool bfs(int src, int end){
        if(isReach[src][end] == 1) return true;
        if(isReach[src][end] == -1) return false;
        queue<int> q;
        q.push(src);
        while(!q.empty()){
            int start = q.front();
            q.pop();
            for(int mid : mp[start]){
                if(!isReach[src][mid]){
                    isReach[src][mid] = 1;  // src -> start可达,start -> mid可达,则src -> mid可达
                    q.push(mid);
                }
            }
        }
        if(isReach[src][end] == 1) return true;
        isReach[src][end] = -1;  // 如果BFS遍历中,isReach[src][end]没被置为1,则说明不可达
        return false;
    }

    vector<bool> checkIfPrerequisite(int n, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {
        isReach.resize(n, vector<int>(n, 0));
        for(auto start_end : prerequisites){
            mp[start_end[0]].push_back(start_end[1]);
        }
        vector<bool> ans;
        for(auto q : queries){
            ans.push_back(bfs(q[0], q[1]));
        }
        return ans;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/476215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI题目整理

1、网络配置时batchsize的大小怎样设置?过小和过大分别有什么特点? Batch size是指一次迭代过程中&#xff0c;输入到神经网络的样本数量。 batchsize太小的缺点&#xff1a; ①耗时长&#xff0c;训练效率低。 ②训练数据就会非常难收敛&#xff0c;从而导致欠拟合。 batch…

MySQL后台线程详解

前言 MySQL的服务实现通过后台多个线程、内存池、文件交互来实现对外服务的&#xff0c;不同线程实现不同的资源操作&#xff0c;各个线程相互协助&#xff0c;共同来完成数据库的服务。本章简单总结MySQL的一些后台线程以及主要作用。 本章收录在MySQL性能优化原理实战专栏&am…

js常见混淆加密技术

下面&#xff0c;我将通过一个案例来演示如何使用JavaScript混淆加密技术来保护你的网站。 假设你有一个网站&#xff0c;其中包含一个登录页面&#xff0c;该页面的JavaScript代码如下所示&#xff1a; function login(username, password) {if (username "admin"…

Doris(21):Doris的函数—日期函数

1 CONVERT_TZ(DATETIME dt, VARCHAR from_tz, VARCHAR to_tz) 转换datetime值dt,从 from_tz 由给定转到 to_tz 时区给出的时区,并返回的结果值。 如果参数无效该函数返回NULL。 select convert_tz(2019-08-01 13:21:03, Asia/Shanghai, America/Los_Angeles); select co…

大数据-玩转数据-初识FLINK

一、初识Flink Flink采用一只松鼠的彩色图案作为logo Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行有状态计算。Flink被设计在所有常见的集群环境中运行&#xff0c;以内存执行速度和任意规模来执行计算 二、Flink的重要特点 1、事件驱动…

mysql与redis区别

一、.redis和mysql的区别总结 &#xff08;1&#xff09;类型上 从类型上来说&#xff0c;mysql是关系型数据库&#xff0c;redis是缓存数据库 &#xff08;2&#xff09;作用上 mysql用于持久化的存储数据到硬盘&#xff0c;功能强大&#xff0c;但是速度较慢 redis用于存储使…

一篇你看得懂的SNP

单核苷酸多态性&#xff0c;&#xff08;Single Nucleotide Polymorphism&#xff0c;简称SNP&#xff09;指的是由单个核苷酸—A,T,C或G的改变而引起的DNA序列的改变&#xff0c;造成包括人类在内的物种之间染色体基因组的多样性。是指在基因组上单个核苷酸的变异&#xff0c;…

朴素贝叶斯分类器with案例:基于SMS Spam Collection数据集的广告邮件分类

目录 贝叶斯分类器何为朴素案例&#xff1a;基于SMS Spam Collection数据集的广告邮件分类SMS数据集词向量表示Laplacian平滑训练过程分类过程 完整代码 贝叶斯分类器 首先要理解贝叶斯决策的理论依据&#xff0c;引用西瓜书上的原话&#xff1a;对于分类任务&#xff0c;在所…

(基础算法)高精度加法,高精度减法

高精度加法 什么叫做高精度加法呢&#xff1f;包括接下来的高精度减法&#xff0c;高精度乘法与除法都是同一个道理。正常来讲的话加减乘除&#xff0c;四则运算的数字都是整数&#xff0c;也就是需要在int的范围之内&#xff0c;但当这个操作数变得非常"大"的时候&…

《面试1v1》java反射

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a; 你好&#xff0c;请问你对 Java 反射有了解吗&#xff1f; 候选人&#xff1a; 是的&#xff0c;我了解一些。 面试官&#xff1a; 那你能简单…

离散数学集合论

集合论 主要内容 集合基本概念 属于、包含幂集、空集文氏图等 集合的基本运算 并、交、补、差等 集合恒等式 集合运算的算律&#xff0c;恒等式的证明方法 集合的基本概念 集合的定义 集合没有明确的数学定义 理解&#xff1a;由离散个体构成的整体称为集合&#xff0c…

【五一创作】【Midjourney】Midjourney 连续性人物创作 ② ( 获取大图和 Seed 随机种子 | 通过 seed 随机种子生成类似图像 )

文章目录 一、获取大图和 Seed 随机种子二、通过 seed 种子生成类似图像 一、获取大图和 Seed 随机种子 注意 : 一定是使用 U 按钮 , 在生成的大图的基础上 , 添加 信封 表情 , 才能获取该大图的 Seed 种子编码 ; 在上一篇博客生成图像的基础上 , 点击 U3 获取第三张图的大图 ;…

电子数据取证之宝塔面板

一、宝塔面板介绍 1、官网bt.com&#xff0c;是提升运维效率的服务器管理软件&#xff0c;支持一键WAMP/LAMP/LNMP等100多项服务器管理功能&#xff1b;是跨平台的软件&#xff0c;同时支持Windows和Linux。开源永久免费。提高工作效率&#xff0c;对小白比较友好。 2、怎么看服…

【网络socket编程----预备知识和UDP服务器模拟实现】

文章目录 一、预备知识1.1 理解IP地址和端口号1.2 认识TCP协议和UDP协议1.3 网络字节序1.4 socket编程接口和sockaddr结构 二、封装 UdpSocket 一、预备知识 1.1 理解IP地址和端口号 众所周知&#xff0c;每台主机都有一个IP地址。而主机和主机之间通信&#xff0c;也需要依赖…

对比学习论文阅读:CoCLR算法笔记

标题&#xff1a;Self-supervised Co-training for Video Representation Learning 会议&#xff1a;NIPS2020 论文地址&#xff1a;https://dl.acm.org/doi/abs/10.5555/3495724.3496201 官方代码&#xff1a;https://www.robots.ox.ac.uk/~vgg/research/CoCLR/ 作者单位&…

软考算法-排序篇-上

数据排序 一&#xff1a;故事背景二&#xff1a;直接插入排序2.1 概念2.2 画图表示2.3 代码实现2.4 总结提升 三&#xff1a;希尔排序3.1 概念3.2 画图表示3.3 代码实现3.4 总结提升 四&#xff1a;直接选择排序4.1 概念4.2 画图表示4.3 代码实现4.4 总结提升 五&#xff1a;堆…

组播PIM协议

PIM&#xff08;Protocol Independent Multicast&#xff09;称为协议无关组播&#xff08;组播分发树&#xff09;。这里的协议无关指的是与单播路由协议无关&#xff0c;即PIM不需要维护专门的单播路由信息。作为组播路由解决方案&#xff0c;它直接利用单播路由表的路由信息…

LeetCode:142. 环形链表 II

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 题解目录 一、&#x1f331;[142. 环形链表 II](https://leetcode.cn/problems/linked-l…

瑞吉外卖:后台系统登录功能

文章目录 需求分析代码开发创建实体类导入返回结果类Rcontroller、service与mapperlogin.html 需求分析 点击登录按钮后&#xff0c;浏览器以POST方式向employee/login提交username和password&#xff0c;服务器经过处理后向浏览器返回某种格式的数据&#xff0c;其中包含&…

Java SE(十一)之异常处理(Exception)

文章目录 异常概述1.什么是异常&#xff1f;2.为什么要异常&#xff1f; 异常体系及分类1.运行时异常2.编译时异常 异常处理1.JVM默认处理方案2.try…catch…3.throw & throws&#xff08;1&#xff09;抛出异常throw&#xff08;2&#xff09;声明异常throws&#xff08;3…