【拓扑排序】课程表系列

news/2024/7/20 23:09:25 标签: 深度优先, 算法

文章目录

  • 课程表(环检测算法
    • 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.niftyadmin.cn/n/276405.html

相关文章

appium的手动安装步骤教程及appium-doctor报错解决集合

前言 相信你不少软件测试行业小伙伴应该在用npm安装appium或者是cpm安装appium途中也碰到下面一些报错吧&#xff0c;接下来Darren洋教你改为手动安装appium吧&#xff01;整理不易&#xff0c;请点赞加关注后查看。 一、安装Node.js 下载地址&#xff1a; Previous Releases …

ctfshow之_萌新web1至web7

一、访问在线靶场ctfshow ctf.showhttps://ctf.show/challenges如下图所示&#xff0c;进入_萌新赛的web1问题&#xff1a; 如上图所示&#xff0c;页面代码提示id1000时&#xff0c;可以查询到flag&#xff0c;进行如下尝试&#xff1a; 如下图所示&#xff0c;传入参数id1时…

lua 实现简单的ip4过滤

lua 实现简单的ip4过滤 最近参与了一个使用openresty开发的服务&#xff0c;有个需求是要某些接口只能允许指定的ip进行访问&#xff0c;在nginx.conf进行配置虽然也能做到限定ip访问&#xff0c;但是却需要重启服务。需求是要求能在界面上配置后不用重启就生效&#xff0c;并…

Codeforces Round 868 (Div. 2) 题解

A. A-characteristic Consider an array a1,a2,…,anconsisting of numbers 1 and −1. Define A-characteristic of this array as a number of pairs of indices 1≤i<j≤n such that ai⋅aj1 Find any array a with given length n with A-characteristic equal to the…

项目沟通管理和干系人管理

与沟通相关的概念、如何制定沟通管理计划、管理和控制沟通、项目干系人管理等 1、沟通的基本概念 沟通&#xff1a;包括信息的生成、传递、接收、理解和检查 沟通渠道的数量和参与者之间成正比关系 沟通渠道的计算公式&#xff1a; M n&#xff08;n - 1&#xff09;/ 2 …

STM32配置ADC2(DMA)进行采集 DAC 输出-2

0.一定要先看上一节&#xff1a;STM32配置ADC2&#xff08;DMA&#xff09;进行采集 DAC 输出-2 1.实验目标 在上一节的基础上&#xff0c;我们把 DAC&#xff08;三角波&#xff09;给集成进来&#xff0c;实现按下按键输出三角波&#xff0c;通过串口发送数据给电脑&#x…

SpringCloud01

SpringCloud01 微服务入门案例 实现步骤 导入数据 实现远程调用 MapperScan("cn.itcast.order.mapper") SpringBootApplication public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}…

三元操作 三元操作符 if-else / ? :

Python 三元操作符 if-else &#xff0c; 其他语言三元操操作符 ? : 。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单……地址&#xff1a;h…