BFS:解决最短路问题

文章目录

  • 什么是最短路问题?
  • 1.迷宫中离入口最近的出口
  • 2.最小基因变化
  • 3.单词接龙
  • 4.为高尔夫比赛砍树
  • 总结

在这里插入图片描述

什么是最短路问题?

最短路问题是图论中的经典问题,旨在寻找图中两个节点之间的最短路径。常见的最短路算法有多种,这次我们讲的主要是以边权为1的最短路问题,什么是边呢?在图论中,权是两个节点的连线的路程。
举个简单的例子:
在这里插入图片描述

下面这个图求A->H的最短路,很明显最短路就是A->E->G->H。
那我们该如何求出这个最短路呢?
在这里插入图片描述
我们可以先将A入进一个队列中,然后将A取出来,将与A相连的两个节点也一起入进去,然后这两个必须一起出队列,这里一起出,并不是同时出而是在一趟中一起出,然后依次循环这个过程,直到找到最后一个节点I,可以看见,最短路的距离其实和入的层数是一样的。这就是我们找到的规律,后面做题需要用到。
接下来我们展示几道例题来更好的理解这个问题:

1.迷宫中离入口最近的出口

题目链接
题目:

在这里插入图片描述

样例输入和输出:

在这里插入图片描述

这道题的意思很简单,题目给我们一个迷宫,这个迷宫是二维数组,二维数组中存放+就表示是障碍,存放.就表示是路,这里的问题是,先给我们一个起始坐标,然后让我们找到最近的出口,这里出口就是边界位置的.,但是有一种情况,当起始位置是边界位置的时候时,这个出口不能被当做出口。问题就是让我们求出到达出口的最短路,就是转化为找到里起始位置最近的.且起始位置不在边界位置的出口。
接下来题目弄懂了,我们来考虑一下算法该怎么做。
算法原理:BFS
这里我们需要一个队列,这个队列存储路的位置,还需要一个vis二维数组,用来标记当前位置已经被走过了,注意这里我们用的广度优先搜索,所以按照上面的图我们应该同时把初始位置的左边位置和上面位置同时入到队列当中,然后pop的时候也要一起pop,做到一层一层的入,我们画个图演示一下在这里插入图片描述

蓝色表示起点。
在这里插入图片描述
很显然,第一次我们将初始节点入进队列,然后第二次同时将深蓝色标记的节点入进队列,第三次将深蓝色标记的节点的相邻的节点入进队列,就找到了,我们一共入了两次,将初始节点给除掉,所以这里最短路是2。

代码展示:

class Solution {
public:bool vis[101][101];int dx[4]={0,0,1,-1};int dy[4]={1,-1,0,0};typedef pair<int,int> PII;int m,n;int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {m=maze.size();n=maze[0].size();queue<PII> q;q.push({entrance[0],entrance[1]});vis[entrance[0]][entrance[1]]=true;int step=0;while(q.size()){step++;int sz=q.size();for(int i=0;i<sz;i++){auto [a,b]=q.front();q.pop();for(int k=0;k<4;k++){int x=a+dx[k];int y=b+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&maze[x][y]=='.'&&vis[x][y]==false){//判断是否走到了终点if(x==0||x==m-1||y==0||y==n-1){return step;}q.push({x,y});vis[x][y]=true;}}}}return -1;}
};

2.最小基因变化

题目链接
题目:

在这里插入图片描述

样例输入和输出:

在这里插入图片描述

最小基因变化这道题,我最开始做的时候也不能将这道题和最短路联系起来,但是这道题确实可以转化为最短路问题,这道题就类似于决策树,首先我们看题意,这道题给我们一个基因序列,则合格序列是由一个八个字符的字符串所构成,这道题给我们一个初始基因序列,给我们一个需要让我们经过变化得到的基因序列,但是这道题并不是让我们直接变化,这道题还给我们一个基因库,我们每次基因序列的变化后的基因序列必须存在于基因库中,这个变化才是有效变化,否则这个变化是无效变化。
接下来我们来举个例子:
在这里插入图片描述
我们用方框表示很多次变化过后,最后变成了我们的最终需要的基因序列,可以看见最开始给我们的初始的基因序列,可以通向很多个基因序列,但是不是每个基因序列我们都是需要的,我们需要在基因库中查找这个变化后的序列是否存在,最后经过很多次变化过后我们得到了最终我们需要的基因序列,我们需要返回的是什么呢?返回我们变化的最少次数。

算法原理:
这道题很显然也可以转化为最短路问题,这道题我们也需要一个队列,然后先用一个hash表将基因库存起来,然后将初始的基因序列入队列,然后我们再开一个hash表,将这个基因序列插入到hash表中标记一下,表示这种变化状态我们已经经历过了。我们还需要把这个字符串的每个位置遍历一遍,因为一次变化一次,有很多种情况,所以我们需要把每个情况遍历一遍,基因序列的每个位置的变化状态我们都必须知道,如果这个状态是满足在基因库中的,我们就入队列,==注意:我们是一层一层的入,第一次变化属于一层,我们需要把所有都在基因库中的存在的状态都入到队列中,顺便标记一下这种状态已经经历过了。然后每次遍历一层之后我们都需要对一个变化次数的变量++,最后这个变化次数就是最小的变化次数。
代码展示:

class Solution 
{
public:
int minMutation(string startGene, string endGene, vector<string>& bank)
{//hash1负责存储访问过的有效基因unordered_set<string> hash1;//hash2负责存储基因库中的基因序列unordered_set<string> hash2(bank.begin(), bank.end());//方便直接遍历的时候用来修改基因序列string change = "ACGT";//当start和end还没遍历就相等时,直接返回0if (startGene == endGene)return 0;//当在hash2中没有找到end的时候也不需要遍历,因为最后的结果没有在基因库中if (!hash2.count(endGene))return -1;//队列存储基因序列queue<string> q;//先插入一个基因序列q.push(startGene);//标记这个基因序列已经被访问过了hash1.insert(startGene);//记录每次操作的步骤int ret = 0;while (q.size()){//先将ret++ret++;int sz = q.size();//每次一层一层的出while (sz--){//用一个临时变量来存储frontstring tmp = q.front();//将队头的变量pop掉q.pop();//遍历每一种可能出现的情况for (int i = 0;i < 8;i++){//如果我们这里每次修改的是tmp的话,第二次我们用的是上次修改过的tmp进行操作,所以这里开一个//临时的string进行操作,每次只进行一次操作string t = tmp;for (int j = 0;j < 4;j++){t[i] = change[j];//改变单个字符//判断是否在基因库中,并且还要满足没被访问过if (hash2.count(t) && !hash1.count(t)){//在入队列的时候,先判断是否已经是最终结果了if (t == endGene){return ret;}//入队列q.push(t);//标记为已访问过hash1.insert(t);}}}}}//没找到返回-1return -1;
}
};

3.单词接龙

题目链接
题目:

在这里插入图片描述

样例输入和输出:

在这里插入图片描述

单词接龙这道题其实和最小基因变化是一样的,也是存在一个单词库,我们每次变化单词都需要考虑这个单词变化后的单词是否存在于单词库中,如果存在于单词库中这次变化才是合法的,如果没有这次不变化不合法,但是,这道题返回的和上道题是不一样的,这道题让我们返回变化过程中出现过的单词数目且是最少的,所以这道题在变化次数的基础上还要+1,因为第一个单词我们还没有算进去,这里算法原理我们就不讲了,和上道题一模一样。
代码展示:

class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) 
{unordered_set<string> hash1;unordered_set<string> hash2(wordList.begin(), wordList.end());int ret = 0;queue<string> q;q.push(beginWord);hash1.insert(beginWord);if (endWord == beginWord)return 1;if (!hash2.count(endWord))return 0;while (q.size()){ret++;//求出队列大小int sz = q.size();//对每一层进行popwhile (sz--){//取出队头元素string tmp = q.front();//pop队头元素q.pop();//对每一种情况进行遍历for (int i = 0;i < beginWord.size();i++){string t = tmp;for (int j = 0;j < 26;j++){t[i] = 'a' + j;if (hash2.count(t) && !hash1.count(t)){if (t == endWord){return ret + 1;}q.push(t);hash1.insert(t);}}}}}return 0;
}
};

4.为高尔夫比赛砍树

题目链接
题目:

在这里插入图片描述

样例输入和输出:

在这里插入图片描述

这道题就优点难度了,首先我们抓关键词,这道题0表示障碍,大于零的数表示可以走的路,大于1的表示树,这里树不是随便砍的,我们需要按照从大到小的顺序砍树,然后返回的是把所有树砍完需要的最小的步数,如果有树砍不到,则返回-1,大致就是这个意思,接下里我们用图画一下这道题。
在这里插入图片描述
大致就是按照这个顺序开砍树的,按照1->2->3->4->5->6。这里我们其实可以看成n个最短路问题,

在这里插入图片描述
我们可以看做1到2的最短路加上2到3的最短路,加上3到4的最短路,再加上4到5的最短路,最后再加上5到6的最短路。看成n个最短路问题。
题目意思了解了,我们来讲讲算法原理:
算法原理:
我们转换为n个最短路问题之后,接下来问题就来了,我们该如何找到顺序呢,砍树的顺序,我们应该优先找到,我们用一个数组存需要砍树位置的小标,然后按照其对应的值进行排序,然后按照数组中排好序的顺序进行排序,每次砍完树的点就是下一次砍下一棵树的起始点然后对每次的起始点和终点做一次BFS找到最短路,这里的路注意是大于等于1的。
这道题我们也需要一个数组来表示每个节点的访问情况,但是需要注意的是,每次访问过后,我们都需要将上一次的访问记录给重置,保证每次都是一个新的访问记录的数组。
代码展示:

class Solution {
public:
typedef pair<int, int> PII;
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };
bool vis[51][51];
int m, n;
int bfs(vector<vector<int>>& forest, int bx, int by, int ex, int ey)
{//如果终点和起点重合则返回0步if (bx == ex && by == ey){return 0;}//定义一个队列queue<PII> q;//对vis进行重置,因为vis为全局变量,每次进入都会记录上次的数据,所以需要重置memset(vis, 0, sizeof vis);//将起点push进队列q.push({ bx,by });//标记对应下标的vis,表示已经访问过vis[bx][by] = true;//记录到终点的步数int step = 0;while (q.size()){//先把这层++step++;//求出这层的大小int sz = q.size();//循环popwhile (sz--){//取队头的坐标auto [a, b] = q.front();//将队头popq.pop();for (int i = 0;i < 4;i++){int x = a + dx[i];int y = b + dy[i];if (x >= 0 && x < m && y >= 0 && y < n && forest[x][y] >= 1 && !vis[x][y]){if (x == ex && y == ey){return step;}q.push({ x,y });vis[x][y] = true;}}}}return -1;
}
int cutOffTree(vector<vector<int>>& forest)
{//求出森林的长和宽m = forest.size(), n = forest[0].size();//创建一个坐标索引数的数组vector<PII> hash;//将对应的有效的坐标存入数组中for (int i = 0;i < m;i++)for (int j = 0;j < n;j++)if (forest[i][j] > 1) hash.push_back({ i,j });//每次比较的时候拿出一个坐标,每次比较的方式是通过forest二维数组对应的坐标的值sort(hash.begin(), hash.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2){return forest[p1.first][p1.second] < forest[p2.first][p2.second];});//按照顺序位置砍树int bx = 0, by = 0;int ret = 0;//对坐标进行遍历for (auto& [a, b] : hash){//起始位置到终止为止的最短近距离int step = bfs(forest, bx, by, a, b);//如果step为-1证明有砍不到的树,则直接返回-1if (step == -1)return -1;//如果能砍掉这颗树则将step累加到ret上ret += step;bx = a, by = b;}//返回retreturn ret;
}
};

总结

通过这篇博客,我们深入探讨了广度优先搜索(BFS)在解决最短路径问题中的应用。从基础概念的介绍到具体算法的实现,我们一步步揭示了BFS的强大之处。BFS的核心在于其逐层搜索的策略,使其在无权图中能够高效地找到从起点到终点的最短路径。

BFS不仅适用于图论中的最短路径问题,还在很多实际场景中发挥着重要作用,例如社交网络分析、迷宫求解和网络爬虫等。通过具体的代码示例和图示,我们不仅展示了BFS的理论知识,也提供了实际应用中的操作指南。

希望通过本文,你不仅掌握了BFS解决最短路径问题的原理和方法,也能够在实践中灵活应用这一强大的算法工具。持续学习和探索,将使你在算法和数据结构的世界中走得更远。感谢你的阅读!

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

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

相关文章

【python包安装】手动安装libmr

遇到问题 再导入libmr模块时&#xff0c;导入失败 尝试使用pip install libmr安装&#xff0c;安装失败 查询原因是windows上pip安装找不到库&#xff0c;只能采取手动安装。 解决方法 下载libMR库文件 安装方法可以查看README文档 安装libmr之前需要安装Microsoft C14或…

开启数字新纪元:全球首款开源AI女友,你的私人数字伴侣

在这个数字化飞速发展的时代,人工智能已经不再是科幻小说中的幻想,而是实实在在走进了我们的生活。今天,我们要介绍的,不仅仅是一项技术革新,更是一场关于陪伴的革命——全球首款开源AI女友,DUIX,已经横空出世! 🚀 革命性的开源平台 DUIX,由硅基智能精心打造,不…

高中数学:数列-等差数列、等比数列的和与通项公式的关系

一、等差数列 1、通项公式与求和公式 2、性质 性质1 求和公式比上n&#xff0c;依然是一个等差数列。 性质2 等差数列中&#xff0c;每相邻m项和&#xff0c;构成的数列&#xff0c;依然是等差数列&#xff0c;公差&#xff1a;m2d 二、等比数列 1、通项公式与求和公式 a…

INVS利用gatearray实现post-mask的function ECO

随着现代IC的设计发展&#xff0c;设计的规模和复杂度逐步增加&#xff0c;对于验证完备性的挑战越来越大&#xff0c;加之TO的时间压力&#xff0c;芯片设计通常会出现下列的场景&#xff1a; 芯片回片一次点亮大部分的case都可以顺利通过小部分的功能需要修正 对于重要的特…

基于CentOS Stream 9平台 安装/卸载 Redis7.0.15

已更正systemctl管理Redis服务问题 1. 官方下载地址 https://redis.io/downloads/#redis-downloads 1.1 下载或上传到/opt/coisini目录下&#xff1a; mkdir /opt/coisini cd /opt/coisini wget https://download.redis.io/releases/redis-7.0.15.tar.gz2. 解压 tar -zxvf re…

.NET C# 装箱与拆箱

.NET C# 装箱与拆箱 目录 .NET C# 装箱与拆箱1 装箱 (Boxing)1.1 过程&#xff1a;1.2 示例&#xff1a; 2 拆箱 (Unboxing)2.1 过程&#xff1a;2.2 示例&#xff1a; 3 性能影响4 性能优化4.1 使用泛型集合示例&#xff1a; 4.2 使用Nullable<T>示例&#xff1a; 4.3 避…

速通数学建模 —— 查找数据

目录 百度搜索技巧 完全匹配搜索&#xff1a;查询词的外边加上双引号“ ” 标题必含关键词&#xff1a;查询词前加上intitle: 搜索文档&#xff1a;空格再输入filetype:文件格式 去掉不想要的&#xff1a;查询词后面加空格后加减号与关键字 知网查文献 先看知网的硕博士…

[AIGC] 深度优先搜索(DFS)详解及其在LeetCode问题中的应用

深度优先搜索&#xff08;Depth-First-Search&#xff0c;简称DFS&#xff09;是一种用于遍历或搜索树或图的算法&#xff0c;其思想是从一个顶点 V0 开始&#xff0c;沿着一条路一直走到底&#xff0c;如果发现不能到达目标解&#xff0c;就退回到上一步的状态&#xff0c;转向…

经典游戏案例:愤怒的小鸟

学习目标&#xff1a;愤怒的小鸟核心玩法 游戏画面 项目结构目录 部分核心代码 using System.Collections; using System.Collections.Generic; using birds; using utils; using UnityEngine;public class GameManager : MonoBehaviour {public static GameManager sInstanc…

【C++】优先队列的使用及模拟实现

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 一、什么是优先队列 二、优先队列的使用 1. 优先队列的构造 2. 优先队列的基本操作 3. 使用示例 三、优先队列模拟实…

【硬件开发】共模电感

为什么电源无论直流还是交流的输入端都需要一个共模电感 图中L1就是共模电感&#xff0c;长下面这个样子&#xff0c;两侧的匝数&#xff0c;线径和材料都是一模一样的 共模电感的作用是为了抑制共模信号 抑制共模信号工作原理 http://【共模电感是如何抑制共模信号的】https…

【免费】中国电子学会2024年03月份青少年软件编程Python等级考试试卷一级真题(含答案)

2024-03 Python一级真题 分数&#xff1a;100 题数&#xff1a;37 测试时长&#xff1a;60min 一、单选题(共25题&#xff0c;共50分) 1. 下列哪个命令&#xff0c;可以将2024转换成2024 呢&#xff1f;&#xff08; A&#xff09;(2分) A.str(2024) B.int(2024) C.fl…

细说AGV的12种导航方式和原理

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 这十二种导航方式各自具有不同的特点和应用场景&#xff0c;下面我将逐一进行简要介绍&#xff1a; 磁钉导航&#xff1a; 原理&#xf…

Python学习笔记17:进阶篇(六)代码测试

代码测试 代码测试是软件开发过程中的关键环节&#xff0c;旨在确保代码质量、功能正确性以及性能符合预期。 在开发过程中&#xff0c;进行代码测试有很多好处&#xff1a; 提高软件质量&#xff1a;通过发现并修复错误&#xff0c;测试有助于提升软件的功能性、可靠性和稳…

LSTM架构的演进:LSTM、xLSTM、LSTM+Transformer

文章目录 1. LSTM2. xLSTM2.1 理论介绍2.2 代码实现 3. LSTMTransformer 1. LSTM 传统的 LSTM (长短期记忆网络) 的计算公式涉及几个关键部分&#xff1a;输入门、遗忘门、输出门和单元状态。 2. xLSTM xLSTM之所以称之为xLSTM就是因为它将LSTM扩展为多个LSTM的变体&#xff…

【LLVM】‘ffast-math’ and ‘ffp-contract’

最近看到一个issue&#xff0c;修改的核心代码部分并不多&#xff0c;可以参考此处的介绍以及此处的issue。 看起来关键就是判断-ffp-contract会将contract的值设为最后一个此选项的值&#xff0c;否则的话&#xff0c;如果只指定了-ffast-math但是没有通过-ffp-contract设置值…

fffdddd

library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all;entity GJL isport(clk, reset: in std_logic;btn_green, btn_red: in std_logic; -- 新增控制按键r1, r2, y1, y2, g1, g2: out std_logic;ledag: out std_logic_…

网络编程--网络理论基础(二)

这里写目录标题 网络通信流程mac地址、ip地址arp协议交换机路由器简介子网划分网关 路由总结 为什么ip相同的主机在与同一个互联网服务通信时不冲突公网ip对于同一个路由器下的不同设备&#xff0c;虽然ip不冲突&#xff0c;但是因为都是由路由器的公网ip转发通信&#xff0c;接…

在Java中使用Apache Kafka进行消息队列处理

在Java中使用Apache Kafka进行消息队列处理 消息队列(Message Queue)是分布式系统中用于异步通信的关键组件,广泛应用于解耦生产者和消费者、平滑流量突增、提高系统弹性等场景。Apache Kafka作为一个高吞吐量、分布式的消息队列系统,已经成为许多企业的首选。本文将介绍如…

Linux 进程管理指令

Linux 进程管理是系统管理的重要部分&#xff0c;通过各种工具和命令&#xff0c;你可以查看、控制、调试和管理进程。以下是一些常用的 Linux 进程管理命令和工具。 查看进程 1. ps ps 命令用于列出当前系统的进程。 查看当前用户的所有进程&#xff1a; ps -u $USER查看…