【算法】BFS系列之 拓扑排序

news2024/9/22 13:17:23

【ps】本篇有 3 道 leetcode OJ。 

目录

一、算法简介

二、相关例题

1)课程表

.1- 题目解析

.2- 代码编写

2)课程表 II

.1- 题目解析

.2- 代码编写

3)火星词典

.1- 题目解析

.2- 代码编写


一、算法简介

【补】图的基本概念

(1)图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:

  • 顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;
  • E = {(x,y)|x,y属于V}或者E = {<x, y>|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。
  • (x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的。
  • 顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。
  • 有向图和无向图:在有向图中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y>和<y, x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边<x, y>和<y, x>。

(2)入度和出度

        图中的度:所谓顶点的度(degree),就是指和该顶点相关联的边数。在有向图中,度又分为入度和出度。

  • 入度 (in-degree) :以某顶点为弧头,终止于该顶点的边的数目称为该顶点的入度。
  • 出度 (out-degree) :以某顶点为弧尾,起始于该顶点的弧的数目称为该顶点的出度。

(3)邻接表

        邻接表存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。

  • 在有向图中,描述每个点向别的节点连的边(“点 a->点 b”这种情况)。
  • 在无向图中,描述每个点所有的边(“点 a-点 b”这种情况)

(4)有向图邻接表存储

          拓扑排序简单来说就是找到做事情的先后顺序(但拓扑排序的结果可能不是唯一的)。 

        一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称 AOV 网。

        在 AOV 网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由 AOV 网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV 网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

        对于有向图的拓扑排序,我们可以使用如下思路输出拓扑序(BFS 方式):

  1. 起始时,将所有入度为 0 的节点进行入队(入度为 0,说明没有边指向这些节点,将它们放到拓扑排序的首部,不会违反拓扑序定义)。
  2. 从队列中进行节点出队操作,出队序列就是对应我们输出的拓扑序。对于当前弹出的节点  x,遍历 x 的所有出度y,即遍历所有由 x 直接指向的节点 y,对 y 做入度减一操作(因为 x 节点已经从队列中弹出,被添加到拓扑序中,等价于 x 节点从有向图中被移除,相应的由 x 发出的边也应当被删除,带来的影响是与 x 相连的节点 y 的入度减一)。
  3. 对 y 进行入度减一之后,检查 y 的入度是否为 0,如果为 0 则将y入队(当y的入度为0,说明有向图中在y前面的所有的节点均被添加到拓扑序中,此时 可以作为拓扑序的某个片段的首部被添加,而不是违反拓扑序的定义)。
  4. 循环流程 2、3 直到队列为空。

        至于如何建图,请见下文例题。

二、相关例题

1)课程表

207. 课程表

.1- 题目解析

        不难看出,这些课程可以构成一个有向无环图,本题其实在问,这些课程构成的有向无环图中是否有环,换句话说,是否可以进行拓扑排序。

        那么,用 BFS 来实现拓扑排序即可。

 

.2- 代码编写

class Solution {
public:
    bool canFinish(int n, vector<vector<int>>& prerequisites) {
        unordered_map<int,vector<int>> edges; //用容器(邻接表)建图
        vector<int> in(n); //标识每一个点的入度
        //1.遍历原始数组建图
        for(auto& e:prerequisites)
        {
            int a=e[0],b=e[1]; //b -> a
            edges[b].push_back(a); //把b指向a的这条边添加入邻接表
            in[a]++; //统计入度
        }
        //2.BFS实现拓扑排序
        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(int a: edges[t]) //遍历t连接的点
            {
                in[a]--; //修改点的入度,相当于删除点
                if(in[a]==0)q.push(a); //继续将入度为0的点入队
            }
        }
        //3,判断是否有环
        for(int i=0;i<n;i++)
            if(in[i])return false;//拓扑排序之后,每个点的入度都应为0

        return true;
    }


};

2)课程表 II

210. 课程表 II

.1- 题目解析

        本题与上道题类似,也是用 BFS 实现拓扑排序,但与上道题判断是否有环不同,本题返回的是拓扑排序的一种结果。

.2- 代码编写

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> edges(numCourses);//用容器(邻接表)存图
        vector<int> in(numCourses);           //统计入度
        //1.建图
        for(auto& v:prerequisites)
        {
            int a=v[0],b=v[1]; // b -> a
            edges[b].push_back(a);
            in[a]++;
        }
        //2.BFS实现拓扑排序
        queue<int> q;
        vector<int> ret;
        for(int i=0;i<numCourses;i++)
        {
            if(in[i]==0)q.push(i);
        }
        while(q.size())
        {
            int t=q.front();q.pop();
            ret.push_back(t);//记录拓扑排序的结果
            for(int a:edges[t])
            {
                in[a]--;
                if(in[a]==0)q.push(a);
            }
        }

        if(ret.size()==numCourses)return ret;
        else return {};
    }
};

3)火星词典

LCR 114. 火星词典

.1- 题目解析

        本题是在问,判断有向图是否有环,因此可以用 BFS 实现拓扑排序来解决。

        特别的,如何搜集信息(如何建图)?——

  1. 两层 for 循环枚举出所有的两个字符串的组合。
  2. 利用指针,根据字典序规则找出信息。

 

.2- 代码编写

class Solution {
    unordered_map<char,unordered_set<char>> edges;//用容器(邻接表)存图
    unordered_map<char,int> in;                   //统计入度
    bool check;                                   //处理边界情况
public:
    string alienOrder(vector<string>& words) {
        //1.建图+初始化入度哈希表
        for(auto& s:words)
            for(auto ch:s)
                in[ch]=0;
        //2.搜集信息
        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(check)//处理边界情况
                    return "";
            }
        //3.拓扑排序
        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);
            }
        }
        //4.判断是否有环
        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;
        for(;i<n;i++)
        {
            if(s1[i]!=s2[i])
            {
                char a=s1[i],b=s2[i]; //a -> b
                if(!edges.count(a) || !edges[a].count(b)) //a没有存过,或a存过但a里没存过b的信息
                {
                    edges[a].insert(b);
                    in[b]++;
                }
                break;
            }
        }
        if(i==s2.size() && i<s1.size())//s1的长度大于s2,就无需进行拓扑排序了
            check=true;
    }
};

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

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

相关文章

本地提权【笔记总结】

文章目录 服务命令at命令提权介绍适用版本复现 sc命令提权介绍适用版本复现 ps应用程序提权复现 进程注入进程迁移注入介绍条件复现 MSF自动化注入介绍getsystem原理 复现 MSF令牌窃取介绍复现 烂土豆提权介绍适用版本复现 UAC绕过介绍复现使用ask模块绕过使用bypassuac_sluihi…

谷歌的AI反击战:创始人谢尔盖·布林的回归与大模型组合的未来

近年来&#xff0c;随着AI技术的迅猛发展&#xff0c;尤其是ChatGPT等大语言模型的出现&#xff0c;全球科技格局正发生剧烈变化。作为曾经引领AI潮流的谷歌&#xff0c;在这场竞争中逐渐失去了领头羊的地位。然而&#xff0c;谷歌的创始人之一谢尔盖布林&#xff08;Sergey Br…

计算组合数

1.递推 #include<bits/stdc.h> #include<unordered_map> #include<unordered_set> using namespace std; #define int long long //可能会超时 #define PII pair<int,int> const int INF 0x3f3f3f3f, mod 1e9 7; const int N 2005; int a, b,n; …

手机自动化测试环境之夜神模拟器inspector部署验证

1、自动化测试环境部署_总览图检查表流程图 Python需要安装Appium-Python-Clicent去定位元素&#xff1b;Appium是一个中间的服务器&#xff0c;它需要依赖node.js&#xff0c;python的脚本通过appium和手机进行交互&#xff1b;手机app的环境都是java环境&#xff0c;先安装jd…

9、等保测评介绍

数据来源&#xff1a;9.等保测评介绍_哔哩哔哩_bilibili 信息系统等级测评 信息系统等级测评是测评机构依据国家信息安全等级保护制度的规定&#xff0c;按照相关管理规范和技术标准&#xff0c;对未涉及国家秘密的信息系统的安全等级保护状况进行检测评估的活动。 等级测评…

gitlab 的CI/CD (一)

前言 GitLab CI/CD 是一个内置在GitLab中的工具&#xff0c;用于通过持续方法进行软件开发&#xff1a; Continuous Integration (CI) 持续集成Continuous Delivery (CD) 持续交付Continuous Deployment (CD) 持续部署 持续集成的工作原理是将小的代码块推送到Git仓库…

JavaEE: 深入探索TCP网络编程的奇妙世界(三)

文章目录 TCP核心机制TCP核心机制三: 连接管理建立连接(三次握手)断开连接(四次挥手)三次握手/四次挥手 流程简图 TCP核心机制 书接上文~ TCP核心机制三: 连接管理 建立连接(三次握手),断开连接(四次挥手). 这里的次数指的是网络通信的次数,挥手/握手是形象的比喻(handshake…

PM2.5粉尘传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 4.粉尘浓度转化关系 5.空气污染指数 三、程序设计 main.c文件 PM25.h文件 PM25.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 GP2Y1014AU是日本夏普公司开发的一款光学灰尘浓度检测传…

探索 Web Speech API:实现浏览器语音识别与合成

引言 Web Speech API 是一项由 W3C 开发的 Web 标准&#xff0c;为开发者提供了在 Web 应用程序中实现语音识别和语音合成的能力。通过 Web Speech API&#xff0c;我们可以让网页与用户进行语音交互&#xff0c;实现更加智能化和便捷的用户体验。本文将深入探讨 Web Speech A…

14 vue3之内置组件trastion全系列

前置知识 Vue 提供了 transition 的封装组件&#xff0c;在下列情形中&#xff0c;可以给任何元素和组件添加进入/离开过渡: 条件渲染 (使用 v-if)条件展示 (使用 v-show)动态组件组件根节点 自定义 transition 过度效果&#xff0c;你需要对transition组件的name属性自定义。…

【Linux】当前进展

驱动层日志添加了下文件目录&#xff0c;函数&#xff0c;代码行的打印&#xff08;这里要小心&#xff0c;驱动目录源代码打印日志里边添进程号可能有问题&#xff0c;因为在驱动初始化的时候&#xff0c;内核还没有创建进程&#xff0c;不过猜测可以先不打印进程相关信息&…

python使用vscode 所需插件

1、导读 环境&#xff1a;Windows 11、python 3.12.3、Django 4.2.11、 APScheduler 3.10.4 背景&#xff1a;换系统需要重新安装&#xff0c;避免后期忘记&#xff0c;此处记录一下啊 事件&#xff1a;20240921 说明&#xff1a;记录&#xff0c;方便后期自己查找 2、插件…

Ansys Zemax | 如何使用琼斯矩阵表面

附件下载 联系工作人员获取附件 概览 琼斯矩阵 (Jones Matrix) 表面是一种非常简便的定义偏振元件的方法。这篇文章通过几个示例介绍了如何使用琼斯矩阵。 介绍 光线追迹程序一般只考虑光线的几何属性&#xff08;位置、方向和相位&#xff09;。光线传播到一个表面时的全…

SQL - 进阶语法(二)约束

1. SQL约束 约束用于约束表中的数据规则&#xff0c;如若存在违反行为&#xff0c;行为会被约束终止。 • NOT NULL 确保列不能有NULL值 如果添加一行新的数据&#xff0c;不能有null值&#xff0c;否则无法添加 新建表格 CREATE TABLE new_table( ID int NOT NULL, NAME …

梯形区域分解实现避障路径规划全覆盖路径规划

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言&#xff08;1&#xff09;功能&#xff08;2&#xff09;算法&#xff08;3&#xff09;参考链接&#xff08;4&#xff09;…

【服务器第二期】mobaxterm软件下载及连接

【服务器第二期】mobaxterm软件下载及连接 前言什么是SSH什么是FTP/SFTP mobaxterm软件介绍mobaxterm软件下载SSH登录使用方法1-新建ssh连接方法2-打开已有的ssh连接方法3-通过ssh命令建立连接 SFTP数据传输方法1-建立ssh连接后直接拖拽方法2-建立sftp连接再拖拽方法3-直接使用…

Nacos配置管理(2)-----配置热更新

有很多的业务相关参数&#xff0c;将来可能会根据实际情况临时调整。例如购物车业务&#xff0c;购物车数量有一个上限&#xff0c;默认是10&#xff0c;对应代码如下&#xff1a; 现在这里购物车是写死的固定值&#xff0c;我们应该将其配置在配置文件中&#xff0c;方便后期…

while(cin>>a)

while(cin>>a)要结束输入CTRLZ换行 输入先调用&#xff1a; istream& operator>> (istream& is, string& str); 但返回值类型时istream&#xff0c; 再调用&#xff1a; 重载的原为(bool)istream&#xff0c;返回值为bool,重载的为括号&#xff0c…

若依前后端分离版项目电子证书查询系统部署到Linux生产环境

项目背景&#xff1a;这个项目之前是PHP语言开发的&#xff0c;采用MVC混编的&#xff0c;前端用Layui框架后端用ThinkPHP8.0框架。客户要求给改成Java语言的&#xff0c;就选用了若依前后端分离低代码版。本地开发调试没有问题&#xff0c;就记录下整个项目上线过程。 服务器背…

How can I stream a response from LangChain‘s OpenAI using Flask API?

题意&#xff1a;怎样在 Flask API 中使用 LangChain 的 OpenAI 模型流式传输响应 问题背景&#xff1a; I am using Python Flask app for chat over data. In the console I am getting streamable response directly from the OpenAI since I can enable streming with a f…