【搜索回溯算法篇】:拓宽算法视野--BFS如何解决拓扑排序问题

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:搜索回溯算法篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.广度优先搜索(BFS)解决拓扑排序
    • 1.拓扑排序简介
    • 2.解决拓扑排序的原理
  • 二.例题
    • 1.课程表
    • 2.课程表||
    • 3.火星词典

一.广度优先搜索(BFS)解决拓扑排序

1.拓扑排序简介

拓扑排序是对有向无环图(DAG, Directed Acyclic Graph)的顶点的一种排序,使得如果存在一条从顶点 u u u 到顶点 v v v 的有向边 ( u , v ) (u, v) (u,v),那么在排序中 u u u 出现在 v v v 之前。它在许多实际应用场景中有重要作用,例如任务调度(每个任务有前置任务要求)、课程安排(先修课程的顺序安排)等。

2.解决拓扑排序的原理

  1. 入度的概念

    • 对于有向图中的每个顶点,我们定义其入度为指向该顶点的边的数量。例如,在有向图中,如果顶点 v v v 有三条边指向它,那么顶点 v v v 的入度为3。
  2. BFS - 基于入度的操作

    • 首先,计算图中每个顶点的入度。
    • 将所有入度为0的顶点放入队列中。这些顶点是可以作为拓扑排序的起始点,因为没有边指向它们,也就没有前置的依赖关系。
    • 然后开始进行BFS:
      • 从队列中取出一个顶点 u u u,将其加入到拓扑排序的结果序列中。
      • 对于顶点 u u u 的所有邻接顶点 v v v,将它们的入度减1(因为 u u u v v v 的边被“处理”了,相当于减少了 v v v 的一个入度来源)。
      • 如果某个邻接顶点 v v v 的入度变为0,就将其加入队列。
    • 重复上述步骤,直到队列为空。
  3. 正确性证明

    • 由于我们首先选择入度为0的顶点加入队列,这些顶点没有前置依赖,可以首先出现在拓扑排序结果中。
    • 当我们处理一个顶点并减少其邻接顶点的入度时,实际上是在逐步消除依赖关系。当一个顶点的入度变为0时,说明它之前的所有依赖都已经被处理过了,所以可以将其加入队列并放入拓扑排序结果中。
    • 因为图是有向无环图,所以这个过程最终会处理完所有顶点,得到正确的拓扑排序结果。

二.例题

1.课程表

题目

在这里插入图片描述

算法原理

首先明白题意要求,给定一个二维数组,每一行中存储的是一对数字,比如示例一(1,0),表示学习课程1之前需要先学习课程0,用有向图表示就是0->1;要求判断给定的所有数字对是否能完成所有课程的学习;

本道题的重点就是如何使用拓扑排序来判断,但是在拓扑排序之前必须是有向图才可以拓扑排序,所以需要先根据给定的数组来建立有向图;建图通常借助两个哈希表或者数组来实现,一个用来表示指向关系,比如a->b;一个用来表示每个节点的入度个数;

在本道题中,因为课程是用数字来表示,正好对应下标,所以可以用数组来实现,这里我写的是用哈希表来表示指向关系,用数组来表示入度个数,具体可以看代码中的注释。

代码实现

bool canFinish(int numCourses, vector<vector<int>>& prerequisites){// 用哈希表来表示邻接表// key值表示一个节点,val表示一个数组,里面存放的是key值节点指向的下一个节点// key=0;val=[1,2,3];表示0指向1,2,3三个节点unordered_map<int, vector<int>> edges;//用数组来存放每个节点的入度,本道题中下标正好对应节点vector<int> in(numCourses);//建图for(auto& nums : prerequisites){//[a,b]表示b->a,在完成a之前先完成bint a = nums[0], b = nums[1];//b->a,存放b的下一个节点edges[b].push_back(a);//节点a入度+1in[a]++;}//将入度为0的入队queue<int> q;for (int i = 0; i < in.size(); i++){if(in[i]==0){q.push(i);}}//拓扑排序while(!q.empty()){//获取队头节点并出队int t = q.front();q.pop();//遍历当前下标对应的所有下一个节点,将对应的节点入度-1,表示删除指向的边for(auto num : edges[t]){in[num]--;//如果对应节点的入度为0,入队if(in[num]==0){q.push(num);}}}//遍历入度数组,如果出现某个节点的入度不为0说明存在环,不能遍历所有节点for (auto num : in){if(num==1){return false;}}return true;
}

2.课程表||

题目

在这里插入图片描述

算法原理

本道题和上面一道题可以说是一模一样,只不过是如果可以完成所有的课程,就输出课程顺序,所以在上一道题的基础上在bfs实现拓扑排序时,只需要将每个出队的头节点存放到数组中即可,因为出队顺序就是拓扑排序的顺序。

代码实现

vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites){//本道题是上一道的变形,具体过程一模一样//用哈希表表示邻接表,数组存放每个节点的入度unordered_map<int, vector<int>> edges;vector<int> in(numCourses);//建表for(auto& e : prerequisites){//b->aint a = e[0], b = e[1];edges[b].push_back(a);in[a]++;}//将所有入度为0的入队queue<int> q;for (int i = 0; i < in.size(); i++){if(in[i]==0){q.push(i);}}//建立一个数组用来返回最终的结果vector<int> ret;//拓扑排序,bfs实现while(!q.empty()){//获取队头节点并出队int t=q.front();q.pop();ret.push_back(t);//通过当前节点遍历所有指向的节点for(auto num : edges[t]){//修改指向的节点的入度in[num]--;//如果入度为0,入队if(in[num]==0){q.push(num);}}}//遍历入度数组,如果出现某个节点的入度不为0,说明存在环,返回空数组for(auto num : in){if(num!=0){ret.clear();return ret;}}return ret;
}

3.火星词典

题目

在这里插入图片描述

算法原理

本道题其实也是根据拓扑排序的原理来解决,题意要求根据词典(words数组)来找到每个字母的先后顺序,然后返回正确的字母顺序;比如,示例一中的"wrt"和"wrf"因为前两个字母是一样的,而在第三个字母出现不同,但又因为"wrt"出现在"wrf"前面,所以字母"t"的顺序在字母"f"的前面,对应题意中的第一种情况;还有就是"abc"和"abcde",因为第一个字符串的长度小于第二个字符串,但是前三个字符又正好相同,没有找到不相同的字符,如果是这种情况,输出的字母顺序那就是字母"de"的顺序在前,然后"abc"三个字母的顺序随便,因为无法判断出这三个字母的顺序;但是可能会出现这种情况第一个字符串是"abcde"而第二个字符串是"abc",这种情况就是违反规则,直接返回空串即可。

所以实现过程还是先根据给定的信息建立有向图,然后拓扑排序,获取信息其实就是将给定的词典数组,两两字符串进行比较获取每个字母的顺序关系,根据上面的两种情况来获取信息。具体的过程注释可以看代码中写的。

代码实现

string alienOrder(vector<string>& words){// 建立一个边哈希表,key值表示字符,val值表示哈希表用来存放该字符指向的所有字符// 因为查找该字符的指向字符时,可能会重复出现,所以内层哈希表用set型去重// 例:key=t;val=[d,c,a];表示t->d&&t->c&&t->a;unordered_map<char, unordered_set<char>> edges;//建立一个入度哈希表,用来存放每个字符的入度//key值表示字符,val值表示该字符对应的入度unordered_map<char, int> in;//先遍历整个词典,将所有出现的字符存放到入度哈希表中并将入度初始化为0,防止后面某些字符没有遍历到for(auto s : words){for(auto ch : s){if(in.count(ch)==0){in[ch] = 0;}}}//两层for循环遍历词典,建AOV图for (int i = 0; i < words.size(); i++){string s1 = words[i];for (int j = i + 1; j < words.size(); j++){string s2 = words[j];//两个指针,一个指向第一个字符串中的起始位置,一个指向第二个字符串的起始位置int cur1 = 0, cur2 = 0;while(cur1<s1.size()&&cur2<s2.size()){//如果两个指针指向对应字符串中的字符不相同,表示s1[cur1]->s2[cur2]//s1的字符指向s2的字符,s2的字符入度+1if(s1[cur1]!=s2[cur2]){//这里有一个细节,如果b->a重复出现,a字符的入度就会重复+1//所以要进行一个判断,如果b字符的哈希表中已经存在字符a,直接跳过if(edges[s1[cur1]].count(s2[cur2])==0){edges[s1[cur1]].insert(s2[cur2]);in[s2[cur2]]++;}//找到第一对不相同的字符就结束break;}else{cur1++;cur2++;}}// 如果两个字符串没有找到相同的字符,有两种情况// 1.s1=ab;s2=abc;不用处理// 2.s1=abc;s2=ab;直接返回空串,因为违反规则if(cur2==s2.size()&&cur1<s1.size()){return "";}}}//设置一个结果字符串用来存放字符的顺序string ret;//设置一个队列queue<char> q;//遍历入度哈希表将所有入度为0的字符入队for(auto& [ch,count] : in){if(count==0){q.push(ch);}}//bfs实现拓扑排序while(!q.empty()){//获取队头字符并出队char ch = q.front();q.pop();//结果字符串加上队头字符ret += ch;//找到该字符指向的所有字符,将指向的字符入度-1,表示删除指向的边for(auto& nextch : edges[ch]){in[nextch]--;//如果指向的字符入度减为0,将该字符入队if(in[nextch]==0){q.push(nextch);}}}//遍历入度哈希表,如果出现某个字符的入度不为0,说明顺序错误,不能将所有字符拓扑排序for(auto& [ch,count] : in){if(count!=0){//直接返回空串return "";}}return ret;
}

以上就是关于bfs解决拓扑排序的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

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

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

相关文章

23.Word:小王-制作公司战略规划文档❗【5】

目录 NO1.2.3.4 NO5.6​ NO7.8.9​ NO10.11​ NO12​ NO13.14 NO1.2.3.4 布局→页面设置对话框→纸张&#xff1a;纸张大小&#xff1a;宽度/高度→页边距&#xff1a;上下左右→版式&#xff1a;页眉页脚→文档网格&#xff1a;勾选只指定行网格✔→ 每页&#xff1a;…

视频脚本生成器(基于openai API和streamlit)

utils.py&#xff1a; # 所有和ai交互的代码放进utils.py里&#xff08;utils 通常是 “utilities” 的缩写&#xff0c;意为 “实用工具” 或 “实用函数”&#xff09;from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from lan…

Android --- CameraX讲解

预备知识 surface surfaceView SurfaceHolder surface 是什么&#xff1f; 一句话来说&#xff1a; surface是一块用于填充图像数据的内存。 surfaceView 是什么&#xff1f; 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中&#xff0c;但在wms 中可以理解为…

Longformer:处理长文档的Transformer模型

Longformer&#xff1a;处理长文档的Transformer模型 摘要 基于Transformer的模型由于自注意力操作的二次复杂度&#xff0c;无法处理长序列。为了解决这一限制&#xff0c;我们引入了Longformer&#xff0c;其注意力机制与序列长度呈线性关系&#xff0c;使其能够轻松处理数…

python学opencv|读取图像(五十二)使用cv.matchTemplate()函数实现最佳图像匹配

【1】引言 前序学习了图像的常规读取和基本按位操作技巧&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;四十九&#xff09;原理探究&#xff1a;使用cv2.bitwise()系列函数实现图像按位运算-CSDN博客…

MySQL为什么默认引擎是InnoDB ?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL为什么默认引擎是InnoDB &#xff1f;】面试题。希望对大家有帮助&#xff1b; MySQL为什么默认引擎是InnoDB &#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认引擎是 InnoDB&#xff0c;主要…

蓝桥杯真题k倍区间

题目如下 代码解析&#xff1a; 成功AC

python项目之requirements.txt文件

Python项目中可以包含一个 requirements.txt 文件&#xff0c;用于记录所有依赖包及其精确的版本号用以新环境部署。 当我们开发新项目的时候&#xff0c;会用virtualenv创建很多python独立环境&#xff0c;这时候就会出现在不同环境下安装相同的模块的情况&#xff0c;这时候…

算法题(53):对称二叉树

审题&#xff1a; 需要我们判断二叉树是否满足对称结构&#xff0c;并返回判断结果 思路&#xff1a; 方法一&#xff1a;递归 其实是否对称分成两部分判断 第一部分&#xff1a;根节点是否相等 第二部分&#xff1a;根节点一的左子树和根节点二的右子树是否相等&#xff0c;根…

使用 cmake

使用前注意 : CMake是一种跨平台的构建系统&#xff0c;它用于管理软件构建过程&#xff0c;尤其适合多语言、多配置的项目。CMake不直接构建软件&#xff0c;而是生成特定构建工具&#xff08;如Makefile或Visual Studio项目&#xff09;所需的配置文件。 如果仅仅使用 qt 编…

AI软件外包需要注意什么 外包开发AI软件的关键因素是什么 如何选择AI外包开发语言

1. 定义目标与需求 首先&#xff0c;要明确你希望AI智能体做什么。是自动化任务、数据分析、自然语言处理&#xff0c;还是其他功能&#xff1f;明确目标可以帮助你选择合适的技术和方法。 2. 选择开发平台与工具 开发AI智能体的软件时&#xff0c;你需要选择适合的编程语言、…

学习数据结构(5)单向链表的实现

&#xff08;1&#xff09;头部插入 &#xff08;2&#xff09;尾部删除 &#xff08;3&#xff09;头部删除 &#xff08;4&#xff09;查找 &#xff08;5&#xff09;在指定位置之前插入节点 &#xff08;6&#xff09;在指定位置之后插入节点 &#xff08;7&#xff09;删除…

深入理解MySQL 的 索引

索引是一种用来快速检索数据的一种结构, 索引使用的好不好关系到对应的数据库性能方面, 这篇文章我们就来详细的介绍一下数据库的索引。 1. 页面的大小: B 树索引是一种 Key-Value 结构&#xff0c;通过 Key 可以快速查找到对应的 Value。B 树索引由根页面&#xff08;Root&am…

Vue-cli 脚手架搭建

安装node.js 官网下载node.js安装包&#xff0c;地址&#xff1a;Node.js — Download Node.js 先在node.js即将要安装的路径下创建两个文件夹&#xff1a;node_cache&#xff08;缓存&#xff09;、node_global&#xff08;全局&#xff09; 点击安装包&#xf…

深度解析 DeepSeek R1:强化学习与知识蒸馏的协同力量

DeepSeek-R1 的问世&#xff0c;无疑在 AI 领域激起了千层浪。自发布仅一周&#xff0c;它便凭借卓越的性能和创新的技术&#xff0c;成为 AI 社区热议的焦点&#xff0c;代表着人工智能在推理和理解能力上的重大飞跃。今天我们一起深度解析一下DeepSeek-R1 一、强大基石&…

openssl 生成证书 windows导入证书

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

笔记本搭配显示器

笔记本&#xff1a;2022款拯救者Y9000P&#xff0c;显卡RTX3060&#xff0c;分辨率2560*1600&#xff0c;刷新率&#xff1a;165Hz&#xff0c;无DP1.4口 显示器&#xff1a;2024款R27Q&#xff0c;27存&#xff0c;分辨率2560*1600&#xff0c;刷新率&#xff1a;165Hz &…

在php中怎么打开OpenSSL

&#xff08;点击即可进入聊天助手&#xff09; 背景 在使用php做一些项目时,有用到用户邮箱注册等,需要开启openssl的能力 在php系统中openssl默认是关闭状态的,在一些低版本php系统中,有的甚至需要在服务器终端后台,手动安装 要打开OpenSSL扩展&#xff0c;需要进行以下步骤 …

【单细胞第二节:单细胞示例数据分析-GSE218208】

GSE218208 1.创建Seurat对象 #untar(“GSE218208_RAW.tar”) rm(list ls()) a data.table::fread("GSM6736629_10x-PBMC-1_ds0.1974_CountMatrix.tsv.gz",data.table F) a[1:4,1:4] library(tidyverse) a$alias:gene str_split(a$alias:gene,":",si…

K8S 快速实战

K8S 核心架构原理: 我们已经知道了 K8S 的核心功能:自动化运维管理多个容器化程序。那么 K8S 怎么做到的呢?这里,我们从宏观架构上来学习 K8S 的设计思想。首先看下图: K8S 是属于主从设备模型(Master-Slave 架构),即有 Master 节点负责核心的调度、管理和运维,Slave…