【搜索回溯算法】:BFS的魔力--如何使用广度优先搜索找到最短路径

news2025/4/21 10:12:33

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

在这里插入图片描述

文章目录

  • 一.广度优先搜索(BFS)解决最短路问题
    • 1.基本思想
    • 2.算法步骤
  • 二.例题
    • 1.迷宫中离入口最近的出口
    • 2.为高尔夫比赛砍树
    • 3.最小基因变化
    • 4.单词接龙

一.广度优先搜索(BFS)解决最短路问题

1.基本思想

  • 广度优先搜索是一种图(或树)的遍历算法。在解决边权相等的最短问题时,他从起始节点开始,逐层的向外扩展搜索。
  • 因为边权相等,所以先被搜索到的节点一定是通过最少的边到达的。

2.算法步骤

  • 初始化
    • 将起始节点标记为已访问,并将其加入到队列中。
    • 记录起始节点到自身的距离为0。
  • 搜索过程
    • 取出队列头部节点
    • 遍历该节点所有未访问的相邻节点
    • 对于相邻节点,标记为已访问,并将其加入到队列中,然后距离加一。
    • 重复上述操作,直到队列为空,或者找到目标节点。

在这里插入图片描述

二.例题

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

题目

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

算法原理

广度搜索,最短路的经典例题,给定迷宫中的起始位置,找到距离出口的最近距离,也就是离开迷宫的最短路径长度。

本道题就是棋盘格式,在二维数组中搜索,所以每一个位置都有上下左右四个方向可以搜索,但是不能越界,具体的搜索方式和上面图中的样例相同。

代码实现

//重命名
typedef pair<int, int> PII;
//两个数组用来四个方向搜索
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

int nearestExit(vector<vector<char>>& maze, vector<int>& entrance){
    //获取行数和列数
    int m = maze.size(), n = maze[0].size();
    //创建一个布尔类型的二维数组
    vector<vector<bool>> check(m, vector<bool>(n));
    //创建一个队列,存放的是数组的下标
    queue<PII> q;
    //将起始位置入队,并标记为已遍历
    q.push({entrance[0], entrance[1]});
    check[entrance[0]][entrance[1]] = true;

    int step = 0;
    while(!q.empty()){
        //每一次外层循环,表示向外扩展一层,层数就是步数,所以步数加一
        step++;
        //获取当前层有多少个下标,依次出队
        int sz = q.size();

        while(sz--){
            auto [a,b]=q.front();
            q.pop();

            //遍历当前位置的四个方向
            for (int k = 0; k < 4; k++){
                int x = a + dx[k], y = b + dy[k];
                if(x>=0&&x<m&&y>=0&y<n&&maze[x][y]=='.'&&check[x][y]==false){
                    //如果符合入队条件并且是出口时,直接返回当前步数
                    if(x==0||x==m-1||y==0||y==n-1){
                        return step;
                    }
                    //如果符合入队条件但不是出口时,入队并标记为已遍历
                    else{
                        q.push({x, y});
                        check[x][y] = true;
                    }
                }
            }
        }
    }

    //如果循环结束也没找到出口,说明不存在出口
    return -1;
}

2.为高尔夫比赛砍树

题目

!在这里插入图片描述

算法原理

因为力扣官方给的示例不是很好,没有完整的描述题目中的规则,所以这里没有展示官方示例,在下面图片中,我会先写一个示例来讲解本道题的题意,然后再写算法原理。本道题可以理解为上面的那道题的扩展,不再局限于一个起始位置和终点位置搜索

在这里插入图片描述

以图中的为例,既然要找到砍树所有树的最少步数,而砍树的顺序是固定的,所以只能限制每一次移动到下一个树时,必须是最短的路径,这不就转换成迷宫问题吗,以上面的砍树顺序为例,先从1到2,最短路径就是1->8->2,最少步数是2,再从2到4,最短路径就是2->8->4或者2->10->4,最少步数就是2,依次类推,将每一次的最少步数累加到一起,就是整个的最少步数。如果出现某个位置到某个位置不能到达时,比如有障碍不能通过,直接返回-1,因为出现某个树不能被砍掉,即使前面的步数再少,也不能砍完所有的树。

代码实现

//重命名
typedef pair<int, int> PII;
//两个数组用来四个方向搜索
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int m = 0;
int n = 0;

int bfs(vector<vector<int>>& forest,int bx,int by,int ex,int ey){
    //如果起始位置就等于终点位置返回0
    if(bx==ex&&by==ey){
        return 0;
    }

    //初始化布尔类型的二维数组,用来标记已遍历的位置
    vector<vector<bool>> check(m, vector<bool>(n));
    //创建一个队列,将起始位置入队
    queue<PII> q;
    q.push({bx, by});
    check[bx][by] = true;

    int ret = 0;

    while(!q.empty()){
        ret++;
        int sz = q.size();

        while(sz--){
            //先获取队头元素
            auto [i, j] = q.front();
            q.pop();

            for (int k = 0; k < 4; k++){
                int x = i + dx[k], y = j + dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&forest[x][y]!=0&&check[x][y]==false){
                    if(x==ex&&y==ey){
                        return ret;
                    }
                    else{
                        q.push({x, y});
                        check[x][y] = true;
                    }
                }
            }
        }
    }

    return -1;
}
int cutOffTree(vector<vector<int>>& forest){
    //获取行数和列数
    m = forest.size();
    n = forest[0].size();
    //新建立一个数组,用来存放每个位置的下标
    vector<PII> trees;
    //遍历整个原始数组,将大于等于1的位置存放到新数组中
    for (int i = 0; i < m; i++){
        for (int j = 0; j < n; j++){
            if(forest[i][j]>1){
                trees.push_back({i, j});
            }
        }
    }

    //按照原数组位置对应的值进行从小到大排序
    //使用Lambda表达式自定义排序规则,[&]是捕获列表,可以访问forest变量,()内是参数列表,{}内是函数体定义比较规则
    sort(trees.begin(), trees.end(), [&](const PII &p1, const PII &p2){
        return forest[p1.first][p1.second] < forest[p2.first][p2.second]; 
    });

    int bx = 0, by = 0;
    int ret = 0;
    for(auto& [a,b] : trees){
        //根据起始位置和终点位置找到最短步数
        int step = bfs(forest, bx, by, a, b);
        //如果返回的最短步数是-1,说明无法从当前起始位置到终点位置,直接输出-1
        if(step==-1){
            return -1;
        }
        //如果返回的不是-1,说明找到最短步数,累加
        ret += step;
        //将当前的终点位置作为下一个的起始位置
        bx = a, by = b;
    }

    return ret;
}

3.最小基因变化

题目

在这里插入图片描述

算法原理

本道题刚开始看可能觉得没有什么思路,但这也是最短路最经典的一个例题,虽然不是棋盘格式的搜索,但是根据题意我们可以自己写出搜索展开图,画出搜索展开图后就能明白为什么是最短路问题以及为什么可以用广度搜索来解。

在这里插入图片描述

本道题有两个细节要处理,一个是如何判断变化后的基因是否重复出现,一个是变化后的基因是否存在与基因库中;

因为基因序列是用字符串的形式表示,所以对于这两个细节的处理都可以借助哈希表来实现。

一个哈希表check用来存放每次变化后的基因,如果存放前发现哈希表中已经存在该变化后的基因,说明重复出现,不再添加到哈希表中。

另一个哈希表hash用来存放基因库中的基因序列,先遍历整个基因库,将所有基因序列存放到哈希表中,题中要求变化后的基因必须存在于基因库中才是有效的基因序列,所以变化后的基因在存放到另一个哈希表前,先判断是否存在基因库中,如果不存在该基因库中就不再添加,存在就继续存放,同时要判断是否重复出现。

搜索的实现方式还是和上面的格式一样,借助队列来实现,循环逐层搜索,直到找到目标基因序列。

代码实现

int minMutation(string startGene, string endGene, vector<string>& bank){
    //建立两个哈希表,一个用来存放变化后的基因,防止相同的基因重复查找
    //一个用来存放基因库中的基因
    unordered_set<string> check;
    unordered_set<string> hash(bank.begin(), bank.end());

    //判断特殊情况,变化前基因等于变化后要查找的基因,直接返回0次
    if(startGene==endGene){
        return 0;
    }
    //如果变化后要查找的基因不在基因库中,直接返回-1
    if(hash.count(endGene)==0){
        return -1;
    }

    //建立一个队列,将变化前的基因入队,并存放到检查哈希表中
    queue<string> q;
    q.push(startGene);
    check.insert(startGene);

    string change = "ACGT";

    int ret = 0;
    while(!q.empty()){
        //每循环一次,相当与向外扩展一层,变化次数加一
        ret++;
        int sz = q.size();

        while(sz--){
            string t=q.front();
            q.pop();

            //i循环表示一个基因8个位置的变化,j循环表示一个位置有4种情况变化
            for (int i = 0; i < 8; i++){
                //这里内层每次循环要拷贝一下原字符串,在拷贝的基础上变化,防止原字符串变化
                string tmp = t;
                for (int j = 0; j < 4; j++){
                    tmp[i] = change[j];
                    //变化后的字符串要存在基因库中才能存放到检查哈希表中,并且不能重复存放
                    if(hash.count(tmp)!=0&&check.count(tmp)==0){
                        //如果满足上面两个条件并且就是结束字符串,直接返回当前变化次数
                        if(tmp==endGene){
                            return ret;
                        }
                        //如果不是结束字符串,入队,并且存放到检查哈希表中
                        else{
                            q.push(tmp);
                            check.insert(tmp);
                        }
                    }
                }
            }
        }
    }

    //循环结束后还没有返回,说明没有找到结束字符串,返回-1
    return -1;
}

4.单词接龙

题目

在这里插入图片描述

算法原理

本道题可以说和上面的那道题最小基因变化一模一样,连代码过程几乎都是一样的,唯一有点不同的就是,上面那道题中一个位置只有四个字符可以变化,而本道题中一个位置有26个字符可以变化,所以在循环时对这点的处理不同,其他地方都是一模一样,所以上面那道题理解之后,本道题就会变得非常简单。

代码实现

int ladderLength(string beginWord, string endWord, vector<string>& wordList){
    //和上一道题基因变化一样

    unordered_set<string> check;
    unordered_set<string> hash(wordList.begin(), wordList.end());

    if(beginWord==endWord){
        return 2;
    }
    if(hash.count(endWord)==0){
        return 0;
    }

    queue<string> q;
    q.push(beginWord);
    check.insert(beginWord);

    int ret = 1;
    while(!q.empty()){
        ret++;
        int sz = q.size();

        while(sz--){
            string t=q.front();
            q.pop();

            for (int i = 0; i < t.size(); i++){
                string tmp = t;
                for (char ch = 'a'; ch <= 'z'; ch++){
                    tmp[i] = ch;

                    if(hash.count(tmp)!=0&&check.count(tmp)==0){
                        if(tmp==endWord){
                            return ret;
                        }
                        else{
                            q.push(tmp);
                            check.insert(tmp);
                        }
                    }
                }
            }
        }
    }

    return 0;
}

以上就是关于使用广度优先搜索解决最短路问题的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

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

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

相关文章

【算法】经典博弈论问题——威佐夫博弈 python

目录 威佐夫博弈(Wythoff Game)【模板】 威佐夫博弈(Wythoff Game) 有两堆石子&#xff0c;数量任意&#xff0c;可以不同&#xff0c;游戏开始由两个人轮流取石子 游戏规定&#xff0c;每次有两种不同的取法 1)在任意的一堆中取走任意多的石子 2)可以在两堆中同时取走相同数量…

CUDA学习-内存访问

一 访存合并 1.1 说明 本部分内容主要参考: 搞懂 CUDA Shared Memory 上的 bank conflicts 和向量化指令(LDS.128 / float4)的访存特点 - 知乎 1.2 share memory结构 图1.1 share memory结构 放在 shared memory 中的数据是以 4 bytes(即 32 bits)作为 1 个 word,依…

力扣动态规划-13【算法学习day.107】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;建议灵神的题单和代码随想录&#xff09;和记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关…

《剪映5.9官方安装包》免费自动生成字幕

&#xff08;避免失效建议存自己网盘后下载&#xff09;剪映5.9官方Win.Mac 链接&#xff1a;https://pan.xunlei.com/s/VOHc-Fg2XRlD50MueEaOOeW1A1?pwdawtt# 官方唯一的免费版&#xff0c;Win和Mac都有&#xff0c;此版本官方已下架&#xff0c;觉得有用可转存收藏&#xf…

Brave132 编译指南 Windows 篇:安装 Visual Studio 2022(二)

1. 引言 在着手编译 Brave 浏览器的 132 版本之前&#xff0c;构建一个完备的开发环境至关重要。Visual Studio 2022 作为一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;为 Brave 浏览器的编译提供了坚实的工具链和技术支持。它不仅提供了高效的代码编辑…

DBO-高斯回归预测matlab

蜣螂优化算法(Dung Beetle Optimizer, DBO)是一种新型的群智能优化算法&#xff0c;在2022年底提出&#xff0c;主要是受蜣螂的的滚球、跳舞、觅食、偷窃和繁殖行为的启发。 本次研究使用的是 Excel 格式的股票预测数据。数据集按照 8&#xff1a;1&#xff1a;1 的比例&#x…

2025美国大学生数学建模竞赛美赛E题成品参考论文(48页)(含模型,可运行代码,求解结果)

2025美国大学生数学建模竞赛E题成品参考论文 目录 一、问题重述 二、问题分析 三、模型假设 四、模型建立与求解 4.1问题1 4.1.1问题1思路分析 4.1.2问题1模型建立 4.1.3问题1代码&#xff08;仅供参考&#xff09; 4.1.4问题1求解结果&#xff08;仅供参考&…

VMware 中Ubuntu无网络连接/无网络标识解决方法【已解决】

参考文档 Ubuntu无网络连接/无网络标识解决方法_ubuntu没网-CSDN博客 再我们正常使用VMware时&#xff0c;就以Ubuntu举例可能有时候出现无网络连接&#xff0c;甚至出现无网络标识的情况&#xff0c;那么废话不多说直接上教程 环境&#xff1a;无网络 解决方案&#…

基于RIP的MGRE VPN综合实验

实验拓扑 实验需求 1、R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址&#xff1b; 2、R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方&#xff1b; R2与R5之间使用ppp的CHAP认证&#xff0c;R5为主认证方&#xff1b; R3与R5之间使用HDLC封…

Autosar-Os是怎么运行的?(Os基础模块)

写在前面&#xff1a; 入行一段时间了&#xff0c;基于个人理解整理一些东西&#xff0c;如有错误&#xff0c;欢迎各位大佬评论区指正&#xff01;&#xff01;&#xff01; 书接上文 Autosar-Os是怎么运行的&#xff1f;&#xff08;一&#xff09;-CSDN博客 目录 1.Resourc…

OPencv3.4.1安装及配置教程

来到GitHub上opencv的项目地址 https://github.com/opencv/opencv/releases/tag/3.4.1 以上资源包都是 OpenCV 3.4.1 版本相关资源&#xff0c;它们的区别如下&#xff1a; (1). opencv-3.4.1-android-sdk.zip&#xff1a;适用于 Android 平台的软件开发工具包&#xff08;SDK…

选择困难?直接生成pynput快捷键字符串

from pynput import keyboard# 文档&#xff1a;https://pynput.readthedocs.io/en/latest/keyboard.html#monitoring-the-keyboard # 博客(pynput相关源码)&#xff1a;https://blog.csdn.net/qq_39124701/article/details/145230331 # 虚拟键码(十六进制)&#xff1a;https:/…

LeetCode题练习与总结:安装栅栏--587

一、题目描述 给定一个数组 trees&#xff0c;其中 trees[i] [xi, yi] 表示树在花园中的位置。 你被要求用最短长度的绳子把整个花园围起来&#xff0c;因为绳子很贵。只有把 所有的树都围起来&#xff0c;花园才围得很好。 返回恰好位于围栏周边的树木的坐标。 示例 1: 输…

< OS 有关 > 阿里云 几个小时前 使用密钥替换 SSH 密码认证后, 发现主机正在被“攻击” 分析与应对

信息来源&#xff1a; 文件&#xff1a;/var/log/auth.log 因为在 sshd_config 配置文件中&#xff0c;已经定义 LogLevel INFO 部分内容&#xff1a; 2025-01-27T18:18:55.68272708:00 jpn sshd[15891]: Received disconnect from 45.194.37.171 port 58954:11: Bye Bye […

使用八爪鱼爬虫和Web Scraper抓取数据实战案例,附详细教程

最近有不少小伙伴咨询怎么抓取抖音视频或者评论的数据&#xff0c;他们多是自媒体或者商家&#xff0c;想要模仿爆火视频或者分析视频评论区的舆情信息&#xff0c;确实呀&#xff0c;现在抖音是流量高地&#xff0c;淘金的地方&#xff0c;真的是一个值得挖掘的宝藏。当然我一…

海外问卷调查渠道查如何设置:最佳实践+示例

随着经济全球化和一体化进程的加速&#xff0c;企业间的竞争日益加剧&#xff0c;为了获得更大的市场份额&#xff0c;对企业和品牌而言&#xff0c;了解受众群体的的需求、偏好和痛点才是走向成功的关键。而海外问卷调查才是获得受众群体痛点的关键&#xff0c;制作海外问卷调…

【C++数论】880. 索引处的解码字符串|2010

本文涉及知识点 数论&#xff1a;质数、最大公约数、菲蜀定理 LeetCode880. 索引处的解码字符串 给定一个编码字符串 s 。请你找出 解码字符串 并将其写入磁带。解码时&#xff0c;从编码字符串中 每次读取一个字符 &#xff0c;并采取以下步骤&#xff1a; 如果所读的字符是…

从ai产品推荐到利用cursor快速掌握一个开源项目再到langchain手搓一个Text2Sql agent

目录 0. 经验分享&#xff1a;产品推荐 1. 经验分享&#xff1a;提示词优化 2. 经验分享&#xff1a;使用cursor 阅读一篇文章 3. 经验分享&#xff1a;使用cursor 阅读一个完全陌生的开源项目 4. 经验分享&#xff1a;手搓一个text2sql agent &#xff08;使用langchain l…

Blazor-选择循环语句

今天我们来说说Blazor选择语句和循环语句。 下面我们以一个简单的例子来讲解相关的语法&#xff0c;我已经创建好了一个Student类&#xff0c;以此类来进行语法的运用 因为我们需要交互性所以我们将类创建在*.client目录下 if 我们做一个学生信息的显示&#xff0c;Gender为…

appium自动化环境搭建

一、appium介绍 appium介绍 appium是一个开源工具、支持跨平台、用于自动化ios、安卓手机和windows桌面平台上面的原生、移动web和混合应用&#xff0c;支持多种编程语言(python&#xff0c;java&#xff0c;Ruby&#xff0c;Javascript、PHP等) 原生应用和混合应用&#xf…