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还没遍历就相等时,直接返回0
    if (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--)
        {
            //用一个临时变量来存储front
            string 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);
                    }
                }
            }
        }
    }
    //没找到返回-1
    return -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();
        //对每一层进行pop
        while (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();
        //循环pop
        while (sz--)
        {
            //取队头的坐标
            auto [a, b] = q.front();
            //将队头pop
            q.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证明有砍不到的树,则直接返回-1
        if (step == -1)return -1;
        //如果能砍掉这颗树则将step累加到ret上
        ret += step;
        bx = a, by = b;
    }
    //返回ret
    return ret;
}
};

总结

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理 | 硬件电路整理

计算机组成原理 | 硬件电路整理 桶形移位器原理图 全加器逻辑框图 多位可控加减法电路逻辑框图 可级联的4位先行进位电路 4位快速加法器 16位组内并行、组间并行加法器 实现原码一位乘法的逻辑框图 补码一位乘法的逻辑框图 无符号数阵列乘法器 原码不恢复余数法硬件逻辑框图 基…

代码随想录第31天|贪心算法

134. 加油站 参考 思路: 以每个油站相差作为判断, 比如: gas [5 8 2 8]cost [6 5 6 6] [-1 3 -4 2]错误 : 把相差最大点当作起点判断能否绕一圈 : 相加数组是否小于0局部最优: 当前累加rest[i]的和curSum一旦小于0&#xff0c;起始位置至少要是i1&#xff0c;因为从i…

中国信通院专访镜舟科技:开源商业化走了多远?

据《2023 中国开源发展蓝皮书》显示&#xff0c;随着数字化转型的深入&#xff0c;开源生态在去年快速发展&#xff0c;开源商业化的模式也逐渐成型。镜舟科技作为开源商业化的先行者&#xff0c;也在技术创新和商业拓展中稳步增长。 日前&#xff0c;中国信息通信研究院&…

【Gradio】如何设置 Gradio 数据框的样式

简介 数据可视化是数据分析和机器学习的关键方面。Gradio DataFrame 组件是一种流行的方式&#xff0c;在网络应用程序中显示表格数据&#xff08;特别是以 pandas DataFrame 对象的形式&#xff09;。 本文将探讨 Gradio 的最新增强功能&#xff0c;这些功能允许用户整合 pand…

21.智能指针(上)

目录 一、概念二、Box\<T\>2.1 概念与应用场景2.2 简单应用2.3 递归类型的创建 三、通过Deref trait将智能指针当作常规引用处理3.1 常规引用3.2 像引用一样使用Box\<T\>3.3 自定义智能指针3.4 函数和方法的隐式解引用强制转换3.5 解引用强制转换与可变性交互 四、…

WPF文本绑定显示格式StringFormat设置-数值类型处理

绑定显示格式设置 在Textblock等文本控件中&#xff0c;我们经常要绑定一些数据类型&#xff0c;但是我们希望显示的时候能够按照我们想要的格式去显示&#xff0c;比如增加文本前缀&#xff0c;后面加单位&#xff0c;显示百分号等等&#xff0c;这种就需要对绑定格式进行处理…

SpringBoot 搭建sftp服务 实现远程上传和下载文件

maven依赖&#xff1a; <dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version> </dependency>application.yml sftp:protocol: sftphost: port: 22username: rootpassword: sp…

【CSS in Depth2精译】1.4 简写属性

文章目录 1.4 简写属性1.4.1 当心简写属性悄悄覆盖其他样式1.4.2 记住简写值的顺序1 上、右、下、左顺序2 先水平、再垂直的顺序 1.4 简写属性 简写属性&#xff08;Shorthand properties&#xff09; 是可以一次性设置多个属性值的样式属性。例如&#xff0c; font 就是一个简…

考前刷题练手感(北航期末往年数据结构编程题)

本次因为是考前一天极速刷题&#xff0c;所以没有讲解&#xff0c;若有问题可私信。 目录 一、 查找同时空人员二、 老鼠回家-无回路三、函数调⽤关系四、东二食堂模拟五、栈帧 一、 查找同时空人员 【问题描述】 假设一共有6个手机基站&#xff0c;都具有记录手机连接基站状…

【MMSegmentation 环境配置】

MMSegmentation 环境配置 1. 创建python 环境2. 安装pytorch3. 安装MMCV4. 安装 MMSegmentation.5. 测试是否安装成功 1. 创建python 环境 conda create --name openmmlab python3.8 -y conda activate openmmlab2. 安装pytorch On GPU platforms: conda install pytorch tor…

C语言第17篇:预处理详解

1、预定义符号 C语言设置了一些预定义符号&#xff0c;可以直接使用。预定义符号也是在预处理期间处理的。 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI…

国产化操作系统杂谈

目录 操作系统国产化背景国产化操作系统名录优秀操作系统介绍1.深度Linux&#xff08;deepin&#xff09;2.FydeOS3.AliOS&#xff08;openAliOS&#xff09;4.openEuler5.红旗Linux6. startOS 总结 操作系统国产化背景 官方的说法是为了打破长期以来国外对中国的操作系统的垄…

高级算法入门必看—21个NPC问题及其证明

文章目录 前言一、布尔可满足性问题二、每子句至多3个变量的布尔可满足性问题&#xff08;3-SAT&#xff09;三、0-1整数规划&#xff08;0-1 integer programming&#xff09;四、Set packing&#xff08;Set packing&#xff09;五、最小顶点覆盖问题&#xff08;Vertex cove…

FOC方案大合集!

获取链接&#xff01;&#xff01;&#xff01; 本次小编给大家带来了一份FOC的方案大合集。此套方案是基于峰岹科技FU68系列MCU的系列方案&#xff0c;包含常用的无感&#xff0c;有感无刷电机的应用&#xff0c;每份方案都包含了原理图&#xff0c;PCB&#xff0c;代码文件&…

GWO-CNN-SVM,基于GWO灰狼优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类)

GWO-CNN-SVM&#xff0c;基于GWO灰狼优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类) 1. GWO灰狼优化算法 灰狼优化算法&#xff08;Grey Wolf Optimizer, GWO&#xff09;是一种启发式优化算法&#xff0c;模拟了灰狼群体的社会行为&#xff0c;包…

2024山东大学软件学院创新项目实训(9)使用OpenCompass进行模型评估

下载好OpenCompassData-core-20231110.zip 之后&#xff0c;解压压缩包 unzip OpenCompassData-core-20231110.zip 运行代码&#xff1a; python run.py --datasets ceval_gen --hf-path /hy-tmp/7B21/merged --tokenizer-path /hy-tmp/7B21/merged --tokenizer-kwargs p…

com域名注册多少钱

COM域名注册价格视具体注册商而定&#xff0c;不同的注册商可能会有不同的收费标准。一般来说&#xff0c;COM域名注册价格在10美元到20美元之间&#xff0c;可根据不同的需求选择注册时间的长短&#xff0c;从1年到10年等不同时间段的注册费用也不同。以下是关于COM域名注册价…

vue3 computed与watch,watchEffect比较

相同点 都是要根据一个或多个响应式数据进行监听 不同点 computed 如要return回来一个新的响应式值&#xff0c;且这个值不允许直接修改&#xff0c;想要修改的话可以设置set函数&#xff0c;在函数里面去修改所依赖的响应式数据&#xff0c;然后计算属性值会基于其响应式依…

板凳-------第58章SOCKET:TCP/IP网络基础

58.1 互联网 互联网会将不同的计算机网络连接起来并允许位于网络中的主机相互之间进行通信。互联网的目标是隐藏不同物理网络的细节以便向互联网中的所有主机呈现一个统一的网络架构&#xff0c;TCP/IP已经成了使用最为广泛的协议套件了&#xff0c; 术语Internet被用来指将全球…

LayoutSystem布局系统

简介: LayoutSystem,是UGUI中由CanvasUpdateSystem发起(m_LayoutRebuildQueue中大部分都是LayoutRebuilder)的关于布局排列的处理系统。 类图: 布局过程 核心代码讲解: LayoutRebuilder