【基础算法总结】BFS 解决拓扑排序

news2024/11/15 11:15:13

BFS 解决拓扑排序

  • 1.拓扑排序简介
  • 2.课程表
  • 3.课程表 II
  • 4.火星词典

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.拓扑排序简介

拓扑排序解决的就是有向无环图的排列问题。接下来先介绍有向无环图,然后是有向无环图的一个应用 AOV网(顶点活动图),然后根据AOV网再来说说什么是拓扑排序,以及实现拓扑排序。

1.有向无环图(DAG图)

有向无环图指的是有方向,但没有环的图。

图就是一个顶点和边连接而成的一个数据结构,有向图就是边都是有方向的。有向无环图就是图中是没有环的。如不能从1->2->3,3->2->4 所以这个图就是一个有向无环图。

在这里插入图片描述
如 4->5->6 是可以走通的,这就不是有向无环图了。
在这里插入图片描述

什么是入度和出度?
入度和出度都是针对一个点来说的

入度:有多少条边指向这个顶点
出度:由这个顶点出去多少条边

在这里插入图片描述

2.AOV网(顶点活动图)

AOV网也叫做顶点活动图,它其实是一个有向无环的一个应用。

在有向无环图中,用顶点来表示一个活动,用边来表示执行活动的先后顺序的图结构

比如我想洗菜,我得先买菜,我想腌肉需要先买菜和准备厨具。。
在这里插入图片描述

3.拓扑排序

概念:略

大白话意思就是,在AOV网中找到做事情的先后顺序。

可以看到在这些活动中,其中一些活动必须在某些活动执行之后才能执行,比如说炒菜,必须先切菜,腌肉。所以在整个工程中这个炒菜绝对不可能先干。
在这里插入图片描述
那些活动可以先干呢?可以看到准备厨具、买菜可以先干,原因就是并没有边执行它们俩。可以先准备厨具,或者先买菜。所以从这里就可以发现一点,拓扑排序的结果可能不是唯一的!

如果先买菜,买完菜之后与买菜相连的箭头就可以删掉了,因为买完菜就可以解锁洗菜的操作了。所以这个箭头就可以删去了。。

在这里插入图片描述

接下来可以准备厨具或者洗菜,原因是它俩都没有其他条件来限制。可以任选一个。
在这里插入图片描述

接下来只能洗菜。。。。。同理重复上面操作,最后我们就可以得到这样的做事情先后的序列,这也是就是拓扑排序的序列。找到做事情的先后顺序。

在这里插入图片描述

如何排序?

  1. 找出图中入度为 0 的点,然后输出
  2. 删除与该点相连的边
  3. 重复1、2操作,直到图中没有点或者没有入度为 0 的点为止

图中没有点理解,所有活动都找完了可以停止了此时的序列就是拓扑排序的序列。

图中没有入度为 0 的点是怎么回事?其实就是在这个拓扑排序中可能面对的不是有向无环图,是有环形结构的。

比如下面这个图,刚开始并不知道这个有向图是不是有环的,所以我们可以先做一下拓扑排序

当把1、3、2拿出来之后,发现剩下的都拿不出来了。原因就是4、5、6形成一个环路,是没有入度为0的边。
在这里插入图片描述

因此这个结束条件还需要加上直到图中没有入度为 0 的点为止 原因就是可能有环!

所以说拓扑排序有一个特别重要的应用,判断有向图中是否有环。

如何判断?
直接对图来一次拓扑排序,当拓扑排序过程中发现没有入度为0的点的时候,但是图中还有剩余点的时候,此时这个图中一定会有环形结构。

4.实现拓扑排序

借助队列,来一次 BFS 即可

  1. 初始化:把所有入度为 0 的点加入到队列中
  2. 当队列不为空的时候
    1. 拿出队头元素,加入到最终结果中
    2. 删除与该元素相连的边
    3. 判断:与删除边相连的点,是否入度变成 0 ,如果入度为 0,加入到队列中

这里还有一个问题没说,如何建图? 如何表示一个点的入度呢?下面的题在说。建完图然后搞拓扑排序。

2.课程表

题目链接: 207. 课程表

题目分析:

在这里插入图片描述

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 ,如 0 、1、2等等。如果给的是一个一个字符串一会建图的时候还需要先把字符串转换成一个一个数才能映射。

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

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

其实这道题问题的就是有向图中是否有环。

我们可以把给的信息抽象称一张有向图,题目问能否完成所有课程学习意思就是能不能把这个课程排个序,说白了就是能否拓扑排序,能否拓扑排序也就是是否这个图是否是有向无环图 —> 有向图中是否有环?

在这里插入图片描述

做一次拓扑排序即可,前面已经说过如何拓扑排序,接下来重点就是如何建图?

如何建图?灵活使用语言提供的容器

  1. 看稠密(看数据量)
    1. 邻接矩阵(稠密图)
    2. 邻接表(稀疏图)

这道题我们用邻接表建图

相像中的邻接表最左边代表某个节点,这个节点右边一坨代表这个点所连接的点。
看起来就像一个个链表,头表示当前所考虑的这个节点,后面相连所有的节点是与我当前点相连的节点。我们建图的目的就是为了方便找到某个点所连接的那个点。不然还要遍历数组去找太麻烦了,所以把这些东西先存到一个数据结构中,这个数据结构就是图。

邻接表我们脑海中想到的应该就是这样的一条一条链表的结构。
在这里插入图片描述

那如何实现一个邻接表呢?

我们没有必须真的搞一个链式结构出来,这里有两种方式:

  1. vector<vector> edges
  2. unordered_map<int,vector> edges

vector嵌套一个vector是比较局限的,只能用于节点里面的值是数字表示的。

unordered_map是比较万能的,可以把int —> string, vector< int > —> vector< string >,等等其他任何类型。

vector嵌套一个vector,通过下标可以找到与这个节点的值相连的其他节点。仅需在对应下标的vector做push_back就可以把与当前节点相连的节点加入。
在这里插入图片描述

用unordered_map就比较万能了,完全像刚才想象出来的邻接表结构,我们这里是一个int的数后面挂了一个int数组,那不就和一个节点挂着一个节点的效果一样的吗。用数组表示所连接的节点。

在这里插入图片描述

  1. 根据算法流程,灵活建图

刚才我们只是实现把所有的边存起来。我们还要根据算法流程多添加一些东西。

比如这里我们是做拓扑排序,因此我们需要直到每个顶点的入度是多少。可以搞一个vector< int > in,数组里面的值就表示这个顶点的入度是多少。

总结:建图先看数据量选择邻接矩阵还是邻接表来建图,然后根据算法流程,灵活的在建图的基础上多添加上一点东西。

class Solution {
public:
    bool canFinish(int n, vector<vector<int>>& prerequisites) {
        
        // 1. 准备工作
        unordered_map<int,vector<int>> edges;//邻接表存图
        vector<int> in(n);// 标记每一个顶点的入度

        // 2. 建图
        for(auto& e : prerequisites)
        {
            int a = e[0], b = e[1];// b -> a 的一条边
            edges[b].push_back(a);// 把与b相连的顶点添加对应数组
            in[a]++;// 记录对应顶点的入度
        }
        
        // 3. 拓扑排序
        queue<int> q;

        // (1) 把所有入度为 0 的顶点加入到队列中
        for(int i = 0; i < n; ++i)
        {
            if(in[i] == 0)
                q.push(i);
        }

        // (2) bfs
        while(q.size())
        {
            int t = q.front();
            q.pop();
            //这道题没有让求顺序

            //把与这个顶点相连的边干掉,就是修改与其相连顶点的入度
            for(auto& a : edges[t])
            {
                in[a]--;//入度-1
                if(in[a] == 0)//入度变为0加入队列
                    q.push(a);
            }
        }

        // 4.判断是否有环
        //如果整个拓扑排序结束之后,每个顶点的入度都变成0了说明没有环,否则就有环
        for(int i = 0; i < n; ++i)
        {
            if(in[i]) return false;
        }
        return true;
    }
};

3.课程表 II

题目连接: 210. 课程表 II

题目描述:

在这里插入图片描述

算法原理:

这道题和上面是一模一样的,还是做一次拓扑排序,不同的是这次要把拓扑排序顺序返回来。

class Solution {
public:
    vector<int> findOrder(int n, vector<vector<int>>& prerequisites) {
        
        // 1. 准备工作
        unordered_map<int,vector<int>> edges;//邻接表存储图
        vector<int> in(n);//存储每一个点的入度

        // 2. 建图
        for(auto& e : prerequisites)
        {
            int a = e[0], b = e[1];
            edges[b].push_back(a);
            in[a]++;
        }

        // 3. 拓扑排序
        queue<int> q;
        vector<int> ret;//统计排序后的结果

        for(int i = 0; i < n; ++i)
        {
            if(in[i] == 0) 
                q.push(i);
        }

        while(q.size())
        {
            int t = q.front();
            q.pop();
            ret.push_back(t);

            for(auto& a : edges[t])
            {
                in[a]--;
                if(in[a] == 0)
                    q.push(a);
            }
        }
        
        if(ret.size() == n) return ret;
        else return {};

    }
};

4.火星词典

题目链接: LCR 114. 火星词典

题目分析:

在这里插入图片描述

现有一种使用英语字母的外星文语言,但是这门语言的字母顺序与英语顺序不同。给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 (也就是说字符串是按照新语言的字母顺序已经进行排序了)。

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

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

  • 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。不用管后面是否还有其他字母。

  • 如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。

在这里插入图片描述

算法原理:

刚才我们是按照随意的顺序去比较搜集信息的,但是在计算机可不能这样,需要按照一定的顺序去比较搜集信息。

  1. 如何搜集信息

两层for循环

在这里插入图片描述

搜集的是两个字符串中第一个不相等字符的信息,就知道谁在前,谁在后。如何统计这个信息呢?由 t 到 f 可以建立一条边。t在前 f在后,正好和有向图的含义是一样的。

在这里插入图片描述

同理经过多次,我们可以得到一个有向图,中间还包含去重重复信息

在这里插入图片描述

如何还原出这些字符串中字母的顺序呢?
最开始可以找一个入度为0的字符加入队列,然后删除和它相连的边,重复上面操作直到图为空或者没有找到入度为0的字符为止。

在这里插入图片描述
发现做一次拓扑排序即可。

  1. 拓扑排序

如何建图?

前面邻接表建图有两种做法

vector<vector<>>
unordered_map<>

这里顶点的值已经不在是int了,不太好对应了。因此还是选unordered_map<>建图。

unordered_map<char,char[]>,char表示当前字符,char[]表示与这个字符相连的其他字符。但是我们搜索的信息是会冗余的,比如wrt和er比较 w->e,wrt和ett比较 w->e,不能无脑全添加到数组里面。所以我们可以判断下在数组中存过就不要在存了,如何快速找呢?可以把char数组继续搞一个hash表,因此终极建图就出来了。

unordered_map<char,unordered_set> edges;
char表示当前字符顶点,第二个hash表示这个顶点所连接的顶点。

在这里插入图片描述
统计入度信息

可以搞一个int数组统计每一个字符入度是多少,但是这里不推荐。这道题并不是所有a-z字符都会出现,如果搞一个int[26],那有的字符出现过,有的压根没出现,那入度给0就会有问题,给-1也没有必要。

直接给一个unordered_map<char,int>,char表示当前字符,int表示当前字符的入度。但是用hash表必须要先把hash表初始化一下。

初始化就是遍历字典中所有字符串每一个字符加入到hash,入度初始化为0。如果不初始化,等会往队列中添加入度为0的字符的时候,你一个也找不到。原因就是你hash表只会存入度大于0的字符。

如何搜集信息

可以利用一个指针来搜集信息。判断当前两个字符串字符是否相等,不相等就右移动,当判断到第一个不相等的时候,就把这个信息丢到
unordered_map<char,unordered_set> edges; , 同时更新一下入度。注意我们只是找第一个不同的字符,如果后面还有其他字符我们是不管的。

在这里插入图片描述

细节问题

我们刚刚所说的东西处理不了这样的字符串比较 “abc” 和 “ab”,前面相等字符串长的必定在后面,所以当发现有这些的字符串,就返回"",刚才拓扑排序解决不了这样的问题,因此特殊处理一下。可以在搜集信息的地方处理,当发现遍历到一个字符串的字符,上面字符还没有结束,但是下面的结束了,此时直接返回空就可以了。

在这里插入图片描述

class Solution {
    unordered_map<char, unordered_set<char>> edges; // 邻接表来存储图
    unordered_map<char, int> in; // 统计⼊度
    bool cheak; // 处理边界情况
public:
    string alienOrder(vector<string>& words) 
    {
        // 1. 建图 + 初始化⼊度哈希表
        for(auto& s : words)
        {
            for(auto ch : s)
            {
                in[ch] = 0;
            }
        }

        int n = words.size();
        for(int i = 0; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
            {
                add(words[i], words[j]);
                if(cheak) return "";
            }
        }
    
        // 2. 拓扑排序
        queue<char> q;
        for(auto& [a, b] : in)
        {
            if(b == 0) q.push(a);
        }

        string ret;
        while(q.size())
        {
            char t = q.front(); q.pop();
            ret += t;
            for(char ch : edges[t])
            {
                if(--in[ch] == 0) q.push(ch);
            }
        }

        // 3. 判断
        for(auto& [a, b] : in)
            if(b != 0) return "";

        return ret;
    }

    void add(string& s1, string& s2)
    {
        int n = min(s1.size(), s2.size());
        int i = 0;
        while(i < n)
        {
            if(s1[i] != s2[i])
            {
                char a = s1[i], b = s2[i]; // a -> b
                if(!edges.count(a) || !edges[a].count(b))//防止重复添加
                {
                    edges[a].insert(b); 
                    in[b]++;
                }
                break;
            }
            ++i;
        }

        //abc  ab
        if(i == s2.size() && i < s1.size()) cheak = true;
    }
};

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

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

相关文章

品牌标志识别检测系统源码分享 # [一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

品牌标志识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着全球经济的快速发展和市场竞争的日益激烈&a…

转mp3格式3步走:在线转换与简易方法大揭秘

现如今&#xff0c;音频格式的转换成了我们生活中的常事。说到音频格式&#xff0c;很多人可能首先想到的就是mp3。它体积小、兼容性强&#xff0c;几乎成了我们手机、电脑里的必备音频格式。那么&#xff0c;怎样才能轻松地把其他格式的音频转mp3格式呢&#xff1f;今天&#…

imx93烧录

本文主要是介绍如何烧录镜像到开发板 这里以evk板为例&#xff1a; 当编译之后&#xff0c;会在目录build/tmp/deploy/images/imx93evk下看到一堆的镜像文件&#xff0c;包括u-boot/kernel/设备树还有完整的镜像 执行下面的命令进行烧录&#xff1a; sudo ./uuu -b emmc_all …

坐牢第三十一天(c++)

一.作业&#xff1a; 使用C手动封装一个顺序表&#xff0c;包含成员数组一个&#xff0c;成员变量N个 #include <iostream> #include <cstring> // 引入cstring以使用memcpy using namespace std; // 类型重命名 using datatype int; // typedef int datatype; s…

【PyTorch][chapter 27][李宏毅深度学习][transformer-1]

前言&#xff1a; transformer 是深度学习四大基础架构之一,最早Google 发表在NIPS&#xff08;NeurIPS 全称神经信息处理系统大会), 是一种seq2seq 的模型.采用的Encoder-Decoder 结构,应用比较广泛。 比如文本生成&#xff0c;语音转换&#xff0c;视频生成. 相对RNN, LSTM …

【软件文档】项目总结报告编制模板(Word原件参考)

1. 项目概要 1.1. 项目基本信息 1.2. 项目期间 1.3. 项目成果 1.4. 开发工具和环境 2. 项目工作分析 2.1. 项目需求变更 2.2. 项目计划与进度实施 2.3. 项目总投入情况 2.4. 项目总收益情况 2.5. 项目质量情况 2.6. 风险管理实施情况 3. 经验与教训 3.1. 经验总结…

position的absolute、relative、fixed

’ 本章基于上图讲解。 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><link rel"icon" href"/favicon.ico"><meta name"viewport" content"widt…

信息安全(密码学)---数字证书、kpi体系结构、密钥管理、安全协议、密码学安全应用

数字证书 数字证书 (Digital Certificate,类似身份证的作用)----防伪标志 CA(Certificate Authority,电子商务认证授权机构)----ca用自己私钥进行数字签名 数字证书 姓名&#xff0c;地址&#xff0c;组织 所有者公钥 证书有效期 认证机构数字签名 ■ 公钥证书的种类与用…

10.2寸工业墨水屏平板

可翻页查看订单信息 按键下载订单 WIFI 漫游通信 IP65 防水防尘

【LeetCode面试150】——48旋转图像

博客昵称&#xff1a;沈小农学编程 作者简介&#xff1a;一名在读硕士&#xff0c;定期更新相关算法面试题&#xff0c;欢迎关注小弟&#xff01; PS&#xff1a;哈喽&#xff01;各位CSDN的uu们&#xff0c;我是你的小弟沈小农&#xff0c;希望我的文章能帮助到你。欢迎大家在…

android MutableLiveData 赋值

Android开发中&#xff0c;MutableLiveData是一个用于管理可观察型数据的类&#xff0c;它是LiveData的一个子类&#xff0c;可以用来传递数据给UI层。 要给MutableLiveData赋值&#xff0c;你需要调用它的setValue(T)方法或者postValue(T)方法。 1、声明代码&#xff1a; cl…

5大热度榜网红机型测评,公布开放式耳机哪个牌子的好用

盛夏时节&#xff0c;天气越来越热&#xff0c;小伙伴们都在抱怨&#xff0c;实在没法戴口罩了。实际上&#xff0c;大家只关注了呼吸&#xff0c;却忽视了一个问题&#xff0c;其实&#xff0c;我们的耳朵也是要“呼吸”的&#xff0c;闷热的天气里&#xff0c;长时间佩戴入耳…

七年老玩家《王者荣耀》分析三:【视觉与音效】

目录 视觉设计 音效设计 结论 王者荣耀中裸眼3D观看模式的技术细节和玩家反馈是什么&#xff1f; 王者荣耀的音乐音效设计过程中&#xff0c;有哪些知名音乐家和音效设计师参与&#xff1f; 王者荣耀如何通过音效增强游戏的情感体验和沉浸感&#xff1f; 王者荣耀中的CG动…

微信小程序消息订阅

官方文档 在微信小程序中实现消息订阅功能&#xff0c;开发者需要遵循微信官方提供的规则和API进行开发。以下是开发者实现微信小程序消息订阅的大致步骤&#xff1a; 1. 申请消息订阅权限 首先&#xff0c;开发者需要在微信公众平台&#xff08;mp.weixin.qq.com&#xff09…

期权定价模型(如Black-Scholes模型)和利率模型中的单因子模型的Python实现案例

一&#xff1a;期权定价模型&#xff08;如Black-Scholes模型&#xff09;的实现 期权定价模型&#xff08;如Black-Scholes模型&#xff09;是用来确定期权合理价格的数学模型。这些模型基于一定的假设&#xff0c;考虑了多种因素&#xff0c;如标的资产价格、期权的行权价格…

redis面试(二十五)CountDownLatch实现

CountDownLatch最基本的原理&#xff0c;就是用来阻塞线程的&#xff0c;java本身也有CountDownLatch&#xff0c;用多线程处理分批处理多数据的时候很有用 基本的逻辑就是&#xff0c;同时开多个子线程&#xff0c;然后主线程进入等待&#xff0c;只有当其他子线程全都结束之…

Windows电脑如何搭建HarmonyOS NEXTDeveloper Preview2环境

Windows电脑如何搭建HarmonyOS NEXTDeveloper Preview2环境&#xff0c;Windows电脑搭建HarmonyOS NEXTDeveloper Preview2环境详解如下&#xff0c;共分为七步&#xff0c;一看就会了。 1、电脑要求以及注意事项 操作系统 &#xff1a; Windows10 64 位、 Windows11 64 位 内…

RK3566 GPIO Set High/Low 不能正常设置

GPIO0_A4 口设置高和低都是High&#xff0c;没办法Low&#xff0c; 通过命令查看USB_SWITCH2 &#xff0c;这个pin 一直是high的。设置不了高也设置不了低。 gpio_direction_output(switch_usb_gpio2, 0); 解决办法&#xff1a; Dts配置 增加pcfg_output_low_pull_down 属性…

Numpy布尔索引与掩码

NumPy 是用于科学计算和处理多维数组数据的最流行的 Python 库之一。NumPy 提供了强大的功能&#xff0c;可以根据布尔条件从数组中索引和提取元素&#xff0c;这称为布尔索引或掩码。掌握布尔索引和掩码可以使用 NumPy 进行高效的数据操作和分析。 本综合指南将解释你需要了解…

0.0 C语言被我遗忘的知识点

文章目录 位移运算(>>和<<)函数指针函数指针的应用场景 strcmp的返回值合法的c语言实数表示sizeof 数组字符串的储存 —— 字符数组与字符指针字符串可能缺少 \0 的情况 用二维数组储存字符串数组其他储存字符串数组的方法 位移运算(>>和<<) 右移(>…