算法:BFS 解决拓扑排序

news2024/11/20 15:20:36

目录

拓扑排序

题目一:课程表

题目二:课程表II

题目三:火星词典


拓扑排序

有向无环图(DAG图)

有向无环图也就是有几个点,每个点之间的线段都是有向的,且任意拿出来几个点,都是无环的,这里的无环指的不仅仅是围成一个圈,而是这个圈的方向要一致,只有围成一个圈且方向一致的情况才叫有环,否则就是有向无环图,即下图就是有向无环图, 即使①②③围成了圈,但是方向不一致:

下面说明两个概念:

入度:指有多少边指向该点,例如③就有两边指向它,所以③的入度为2
出度:指有多少边从该点出去,例如①就有两边出去,所以①的出度为2


AOV网:顶点活动图

AOV网也就是在有向无环图中,用一个顶点来表示一个活动,用边来表示活动的先后顺序的图的结构

AOV网是有实际意义的


拓扑排序

拓扑排序就是找到做事情的先后顺序,这里的顺序可能不是唯一的

因为每次排序时,可能有多个顶点入度为0,此时选择哪个顶点都可以,所以顺序可能不是唯一的

如何排序

①找到图中入度为0的点
②删除与改点相连的点
③重复上面的①②操作,直到图中没有点或是没有入度为0的点为止

如果有环就会出现没有入度为0的点

所以拓扑排序的重要应用就是:判断图中是否有环


实现拓扑排序

拓扑排序就是借助队列,进行一次bfs即可

1、初始化,将所有入度为0的点入队列

2、当队列不为空的时候:
①拿出队头元素,加入到最终结果中
②删除与该元素相连的边
③判断:与删除边相连的点,是否入度变为0,如果入度为0,加入到队列中


题目一:课程表

你这个学期必须选修 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, 0] 就表示在有向图中,方向是由0指向1的

那么如何建图呢

如果点比较稀疏,就采用邻接表建图,如果比较稠密,就采用邻接矩阵建图

邻接表就是每一个点都拿出来,在后面加上它连接的点

可以采用 vector<vector<int>> 或是 unordered_map<int, vector<int>> 这两种方式创建

第一种方式:就相当于创建一个二维数组,类似于实现了一个链式结构

第二种方式:相当于每个int后面都挂了一个数组,是一样的

因为拓扑排序最关键的就是要知道每一个顶点的入度,每个顶点的入度就使用vector<int>表示即可

代码如下:

class Solution 
{
public:
    bool canFinish(int num, vector<vector<int>>& prerequisites) 
    {
        // 1、准备工作
        unordered_map<int, vector<int>> edges; // 邻接表
        vector<int> in(num); // in数组存储每一个顶点的入度

        // 2、建图
        for(auto& it : prerequisites)
        {
            // 这里需要注意,是b指向a
            int a = it[0], b = it[1];
            // b的后面加上a,a的入度++
            edges[b].push_back(a);
            in[a]++;
        }

        // 3、拓扑排序
        queue<int> q;
        for(int i = 0; i < num; i++)
        {
            // 入度为0的顶点全部入队列
            if(in[i] == 0) q.push(i);
        }

        // 4、bfs
        while(!q.empty())
        {
            int top = q.front();
            q.pop();
            for(auto& it : edges[top])
            {
                in[it]--;
                if(in[it] == 0) q.push(it);
            }
        }

        // 5、判断是否有环
        for(int i = 0; i < num; i++)
            if(in[i]) return false;
        return true;
    }
};

题目二:课程表II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]

示例 3:

输入:numCourses = 1, prerequisites = []
输出:[0]

这道题和上道题几乎一模一样,只不过上道题判断的是是否有环,只需要将是否有环的结果返回即可, 这道题如果无环,需要将无环的其中一种顺序返回,不需要多做说明,代码如下:

class Solution 
{
public:
    vector<int> findOrder(int num, vector<vector<int>>& prerequisites) 
    {
        // edges是邻接表,in是每个点的入度情况,ret是最终返回的上课顺序
        unordered_map<int, vector<int>> edges;
        vector<int> in(num);
        vector<int> ret;

        for(auto& it : prerequisites)
        {
            // b指向a
            int a = it[0], b = it[1];
            edges[b].push_back(a);
            in[a]++;
        }
        // 入度为0的顶点全部入队列
        queue<int> q;
        for(int i = 0; i < num; i++)
        {
            if(in[i] == 0) q.push(i);
        }
        // bfs
        while(!q.empty())
        {
            int top = q.front();
            q.pop();
            ret.push_back(top);
            for(auto& it : edges[top])
            {
                in[it]--;
                if(in[it] == 0) q.push(it);
            }
        }
        // 判断是否有环
        if(ret.size() == num) return ret;
        return {};
    }
};

题目三:火星词典

现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。

给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。

请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。

字符串 s 字典顺序小于 字符串 t 有两种情况:

  • 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
  • 如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。

示例 1:

输入:words = ["wrt","wrf","er","ett","rftt"]
输出:"wertf"

示例 2:

输入:words = ["z","x"]
输出:"zx"

示例 3:

输入:words = ["z","x","z"]
输出:""
解释:不存在合法字母顺序,因此返回 "" 。

这道题也就是告诉我们根据给出的words字符串数组,得到这个星球的字母顺序

在words数组中,先出现的字符串的字典序就小于后出现的字典序

举个例子,如果 words = ["th", "ta"]

根据words数组的顺序,得出 "th" 的字典序在 "ta" 之前, 而两个字符串的第一个字符t相等, 所以就根据第一个不同的字符比较字典序,"th"和"ta"的第二个字符不同,分别是 'h' 和 'a',又因为 "th"  在 "ta" 前,所以说明 h 的字典序小于 a

根据上面的例子,可以知道,想要得到字典序,只需要找到两个字符串第一个不同的字符,将这两个字符进行比较即可,所以在此题中,进行两次for循环,将每两个字符串都做比较,最终得到很多组指向,例如上述的例子中,就可以得到 h 的字典序在 a 之前,可以得到 h -> a

那么将这些所有得到的信息,形成一个有向无环图,最终进行拓扑排序即可

下面有几点需要注意的地方:

①建图的哈希表unordered_map,在之前的题目都是 unordered_map<char, vector<char>>这种的,但是此题一个顶点后面有可能会出现重复插入的情况,所以将后面的vector也替换为哈希表

②统计入度信息时,最好不使用vector<char>了,因为此题是char类型的数据,如果想使用char类型的数组,需要开26个空间,如果其中大部分的字符没有出现,会造成空间浪费的情况,所以这里统计入度信息时,也采用哈希表unordered_map<char, int>来统计

使用哈希表统计需要注意的是需要初始化,将所有出现的字符全部初始化为0,因为如果不初始化为0,哈希表只会统计有入度的字符

③收集信息时,需要使用双指针的操作,找到两个字符串第一个不同的字符

④如果有字符串是 "abc" , "ab",也是不合法的,因为如果这两个字符串前面的"ab"相等,那么"abc" 一定是在 "ab" 后面的,如果出现在 "ab" 前面,就是不合法的

代码如下:

class Solution 
{
    // edges邻接表,in存储每个顶点的入度数,ret存储最终结果
    unordered_map<char, unordered_set<char>> edges;
    unordered_map<char, int> in;
    bool flag;
public:
    string alienOrder(vector<string>& words) 
    {
        // 处理特殊情况
        if(words.size() == 1) return words[0];
        // 初始化入度哈希表
        for(int i = 0; i < words.size(); i++)
            for(auto& ch : words[i])
                in[ch] = 0;

        // 两层for循环,将words数组的字符串两两组合
        for(int i = 0; i < words.size(); i++)
        {
            for(int j = i + 1; j < words.size(); j++)
            {
                // 找两个字符串中不同的字符,添加进edges中
                add(words[i], words[j]);
                if(flag) return "";
            }
        }
        // 拓扑排序
        queue<char> q;
        for(auto& [a, b] : in)
        {
            if(b == 0) q.push(a);
        }
        string ret;
        while(!q.empty())
        {
            char top = q.front();
            q.pop();
            ret += top;
            for(auto& it : edges[top])
            {
                in[it]--;
                if(in[it] == 0) q.push(it);
            }
        }
        // 判断是否有环
        for(auto& [a, b] : in)
            if(b) return "";
        return ret;
    }

    void add(const string& s1, const string& s2)
    {
        int cur = 0;
        int n = min(s1.size(), s2.size());
        while(cur < n)
        {
            if(s1[cur] != s2[cur])
            {
                // a -> b
                char a = s1[cur], b = s2[cur];
                // a不存在,或a存在,但a对应的哈希表中没有b
                if(!edges.count(a) || !edges[a].count(b))
                {
                    edges[a].insert(b);
                    in[b]++;
                }
                break;
            }
            cur++;
        }
        // "abc"和"ab"的情况
        if(cur == s2.size() && cur < s1.size())
            flag = true;
    }
};

算法:BFS 解决拓扑排序到此结束

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

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

相关文章

Adaboost集成学习 | Matlab实现基于ELM-Adaboost极限学习机结合Adaboost集成学习故障诊断

目录 效果一览基本介绍程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | Matlab实现基于ELM-Adaboost极限学习机结合Adaboost集成学习故障诊断 ELM(Extreme Learning Machine)和 Adaboost 都是机器学习领域中常见的算法。ELM 是一种单隐层前馈神经网络,具有快速训练、…

使用 1panel面板 部署 springboot 和 vue

代码仓库&#xff1a;还没弄 目录 网站介绍安装步骤1. 准备云服务器2. 准备域名&#xff08;可跳过&#xff09;3. 安装1panel面板4. 服务器开放端口5. 进入1panel面板6. 安装并启动软件&#xff08;服务器和面板开放端口&#xff09;7. 打包并上传项目7.1 打包 Java项目&#…

算力共享中神经网络切片和算力分配策略

目录 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进行映射 算力分配策略 get_current_shard 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进…

基于RK3588+AI支持能源在线监测系统应用的AIOT产品方案

支持能源在线监测系统应用的AIOT产品方案 近年来&#xff0c;智慧能源行业受益于国家政策扶持、市场需求拉动和先进技术支撑呈现出了蓬勃发展态势。助推智慧能源发展&#xff0c;打造了支持能源在线监测系统应用的AIOT产品方案。 能源在线监测系统的市场潜力 随着社会经济的飞…

C++笔记---类和对象(中)

1. 类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。 一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;分别为&#xff1a;构造函数&#xff0c;析构函数&#xff0c;拷贝构…

【Java】Java swing 民宿管理系统 GUI(源码+可视化界面)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【vulnhub】W1R3S.inc靶机

靶机安装 下载地址&#xff1a;https://download.vulnhub.com/w1r3s/w1r3s.v1.0.1.zip 运行环境&#xff1a;vmware 信息收集 靶机发现IP扫描 nmap 192.168.93.0/24 端口扫描,发现开放21、22、80、3306端口 nmap -A 192.168.93.159 -p- 进行目录扫描 dirsearach -u http…

函数实例讲解(三)

文章目录 常用的三个数学函数1、绝对值函数ABS2、取整数部分INT3、求余数函数MOD 求极值函数max、min1、Max2、Min 附加条件下求平均数1、AVERAGE2、AVERAGEIF3、AVERAGEIFS VLOOKUP与COLUMN1、VLOOKUP2、COLUMN 查找函数LOOKUP1、基础语法2、向量形式3、数组形式 常用的三个数…

Odoo生产执行(MES)系统管理解决方案简介

什么是生产执行管理解决方案&#xff1f; Odoo生产执行管理解决方案可以在统一平台上集成诸如生产调度、产品跟踪、质量控制、设备故障分析、网络报表等管理功能&#xff0c;使用统一的数据库和通过网络联接可以同时为生产部门、质检部门、工艺部门、物流部门等提供车间管理信息…

windows 部署 mindspore GPU 开发环境(WSL)

基础环境 windows 环境&#xff1a; Windows 10 版本&#xff1a;22H2 操作系统版本&#xff1a;22621.2283 WSL 系统 版本&#xff1a;2.2.4.0 Ubuntu-20.04 一、自定义位置安装Ubuntu 确保已经安装 WSL 在微软应用商店搜索时务必输入全名 Ubuntu20.04&#xff0c;并安装…

LLM:SGD、adam

SGD没有一阶和二阶动量。adam是融合了这两种动量。 参考&#xff1a;https://blog.csdn.net/yinyu19950811/article/details/90476956 【十分钟搞明白Adam和AdamW&#xff0c;SGD&#xff0c;Momentum&#xff0c;RMSProp&#xff0c;Adam&#xff0c;AdamW】

【window10/window11】解决任务管理器有进程无法强制结束情况

以管理员身份启动控制台窗体&#xff0c;然后从任务管理器中查询到你要结束的进程名&#xff0c;然后运行以下命令&#xff08;UniAccessAgent.exe替换成你要结束的进程&#xff09;&#xff1a; wmic process where nameUniAccessAgent.exe delete 此方法可以解决在任务管理…

微软蓝屏事件揭示的网络安全深层问题与未来应对策略

目录 微软蓝屏事件揭示的网络安全深层问题与未来应对策略 一、事件背景 二、事件影响 2.1、跨行业连锁反应 2.2、经济损失和社会混乱 三、揭示的网络安全问题 3.2、软件更新管理与风险评估 3.2、系统复杂性与依赖关系 3.3、网络安全意识与培训 四、未来的网络安全方向…

LSPatch制作内置模块应用软件无需root 教你制作内置应用

前言 LSPatch功能非常强大&#xff0c;它是一款基于LSPosed核心的免Root Xposed框架软件。这意味着用户无需进行手机root操作&#xff0c;即可轻松植入内置Xposed模块&#xff0c;享受更多定制化的功能和体验&#xff0c;比如微某内置模块版等&#xff0c;这为那些不想root手机…

分享一个基于Spring Boot的在线智慧考公学习管理系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

【Vue3】组件通信之provideinject

【Vue3】组件通信之provide&inject 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努…

一款好用的开源网站内容管理系统

今天给大家介绍的是一款开源网站内容管理系统&#xff08;灵活、易用&#xff0c;性能良好、运行稳定&#xff0c;轻松管理建设网站&#xff09; 官网&#xff1a;https://www.ujcms.com/ 介绍 客户端兼容Edge&#xff08;Chromium版&#xff09;、谷歌浏览器&#xff08;Chro…

Mybatis实战:图书管理系统(笔记)

前言&#xff1a;如果在接口的声明方法中鼠标右键没有Test的单元测试。 你的鼠标光标问题&#xff1a;要在花括号范围内&#xff01;&#xff01;&#xff01;&#xff01; 数据库表是应⽤程序开发中的⼀个重要环节, 数据库表的设计往往会决定我们的应⽤需求是否能顺利实现, 甚…

数据湖和数据仓库核心概念与对比

随着近几年数据湖概念的兴起&#xff0c;业界对于数据仓库和数据湖的对比甚至争论就一直不断。有人说数据湖是下一代大数据平台&#xff0c;各大云厂商也在纷纷的提出自己的数据湖解决方案&#xff0c;一些云数仓产品也增加了和数据湖联动的特性。但是数据仓库和数据湖的区别到…

【LeetCode每日一题】2024年8月第一周(下)

2024.8.03 中等 链接&#xff1a;3143. 正方形中的最多点数 &#xff08;1&#xff09;题目描述&#xff1a; &#xff08;2&#xff09;示例 &#xff08;3&#xff09;分析 题目中以s字符串中&#xff1a;相同的字母 为限制&#xff0c;要求方格内只包含不同字母对应的点位。…