【C++】BFS解决边权唯一的最短路径问题

news2024/12/27 0:24:27

目录

介绍

迷宫中离入口最近的出口

算法思路

代码实现

最小基因变化

算法思路

代码实现

单词接龙

算法思路

代码实现

为高尔夫比赛砍树

算法思路

代码实现


介绍

最短路问题是图论中非常经典的一种问题,其实就是通过代码找到两点之间的最优路径(往往是距离最短),最短路问题的解法很多,比如A*算法,迪杰斯特拉算法等等,本文介绍最短路问题中最简单的一种边权为1的最短路问题。

所谓的边权,就是指两个地点之间的距离为1(如下图所示)

很明显,实际情况的道路更加复杂,两个地点之间的距离不能全是1,所以边权为1的最短路问题是比较特殊,简单的最短路问题

要记录整个过程的最短路,可以通过bfs来解决,选定一个地点,以这个地点为中心向外扩展(因为一个地点连接着很多的地点)

在扩散的时候来时的路是不需要再扩散回去的,所以就需要使用进行过标记。

下面是几道例题

迷宫中离入口最近的出口

例题地址:. - 力扣(LeetCode)

给你一个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格子(用 '.' 表示)和墙(用 '+' 表示)。同时给你迷宫的入口 entrance ,用 entrance = [entrancerow, entrancecol] 表示你一开始所在格子的行和列。

每一步操作,你可以往  或者  移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance 最近 的出口。出口 的含义是 maze 边界 上的 空格子entrance 格子 不算 出口。

请你返回从 entrance 到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1 。

示例 1:

输入:maze = [["+","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
输出:1
解释:总共有 3 个出口,分别位于 (1,0),(0,2) 和 (2,3) 。
一开始,你在入口格子 (1,2) 处。
- 你可以往左移动 2 步到达 (1,0) 。
- 你可以往上移动 1 步到达 (0,2) 。
从入口处没法到达 (2,3) 。
所以,最近的出口是 (0,2) ,距离为 1 步。

算法思路

这道题给出了起点,让求出到达终点最小的路径,也就是走了几步到达了终点,终点就是矩阵的边缘且不能在墙上走。

首先进行准备工作,定义好矩阵的边界,和遍历需要的dx,dy数组,以及标记数组。

1.在nearestExit函数中只需要调用一次bfs即可;
2.bfs函数实现:count来统计走的步数,老一套的模版了,不懂得可以看博客CSDN,不同的是,这里我们每次进行一层的遍历都需要把当前层的全部都删除,这样我们在下一层中找到合法的数据,在进行插入队列。在每次插入新的元素时千万别忘记了标记,因为走过的路是不需要再回去的。

代码实现

class Solution {
public:
    int m,n;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    bool vis[101][101];
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        m=maze.size();n=maze[0].size();
        int i=entrance[0];int j=entrance[1];
        return bfs(maze,  i, j);
    }
    int bfs(vector<vector<char>>& maze, int i,int j)
    {
        int count=0;//每次遍历一层就步数+1
        queue<pair<int,int>>q;
        q.push({i,j});
        vis[i][j]=true;
        while(q.size())
        {
            count++;
            int size=q.size();//将这一层的都派出去
            for(int r=0;r<size;r++)
            {
            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])
                {
                    if(x==0||x==m-1||y==0||y==n-1)return count;//如果到达边界直接返回
                    q.push({x,y});
                    vis[x][y]=true;
                }
            }
            }
        }
        return -1;
    }
};

最小基因变化

例题地址:. - 力扣(LeetCode)

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A''C''G' 和 'T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

  • 例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。

另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

示例 1:

输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"]
输出:1

示例 2:

输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2

示例 3:

输入:start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"]
输出:3

算法思路

这道题也是一道最短路问题,第一次进行变化有这么多种的情况,然后下一次图中的每一种情况会在进行变化一个字符,然后推演出更多种的基因序列,直到出现了最后目标的基因序列,就算是达到了终点。我们每走一步就枚举处所有的情况,然后把在基因库中存在的基因序列且没有出现过的放在队列中,回到最后目标基因出现。

代码实现

class Solution {
public:
    int minMutation(string startGene, string endGene, vector<string>& bank) {
        set<string>hash(bank.begin(),bank.end());
        set<string>vis;
        string change="ATGC";//用来枚举改变的基因;
        if(startGene==endGene)return 0;///如果一开始基因就是目标基因,那么直接返回即可;
        if(!hash.count(endGene))return -1;//如果目标基因不在基因库中那么就无需操作了
        queue<string>q;
        q.push(startGene);
        vis.insert(startGene);
        int step=0;
        while(q.size())
        {
            step++;
            int ret=q.size();
            while(ret--)
            {
                string t=q.front();
                q.pop();
                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)&&!vis.count(tmp))
                        {
                            if(tmp==endGene)return step;
                            q.push(tmp);
                            vis.insert(tmp);
                        }
                    }
                }
            }     
            
        }
        return -1;
    }
};

单词接龙

例题地址:. - 力扣(LeetCode)

字典 wordList 中从单词 beginWord 到 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。
  •  对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

算法思路

这道题与上一道题是思路是相同的,只是让统计的是变化中出现单词的个数;

代码实现

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        set<string>hash(wordList.begin(),wordList.end());
        set<string>vis;
        if(beginWord==endWord)return 1;
        if(!hash.count(endWord))return 0;
        queue<string>q;
        q.push(beginWord);
        vis.insert(beginWord);
        int ret=1;
        while(q.size())
        {
            ret++;
            int sz=q.size();
            while(sz--)
            {
                string t=q.front();
                q.pop();
                for(int i=0;i<beginWord.size();i++)
                {
                    string tmp=t;
                    for(char ch='a';ch<='z';ch++)
                    {
                        tmp[i]=ch;
                        if(hash.count(tmp)&&!vis.count(tmp))
                        {
                            if(tmp==endWord)return ret;
                            q.push(tmp);
                            vis.insert(tmp);
                        }
                    }
                }
            }
        }
        return 0;
    }
};

为高尔夫比赛砍树

例题地址:. - 力扣(LeetCode)

你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n 的矩阵表示, 在这个矩阵中:

  • 0 表示障碍,无法触碰
  • 1 表示地面,可以行走
  • 比 1 大的数 表示有树的单元格,可以行走,数值表示树的高度

每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。

你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1(即变为地面)。

你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。

可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。

示例 1:

输入:forest = [[1,2,3],[0,0,4],[7,6,5]]
输出:6
解释:沿着上面的路径,你可以用 6 步,按从最矮到最高的顺序砍掉这些树。

算法思路

不能再0上走,砍树的顺序要从低到高,因此我们需要先按照树的高度对左边进行一个排序,然后使用BFS进行多次调用,得到总路程;

代码实现

class Solution {
public:
    int m,n;
    int cutOffTree(vector<vector<int>>& forest) {

        m=forest.size();n=forest[0].size();
        //准备工作,将要砍的树的坐标进行排序
        vector<pair<int,int>>trees;
        for(int i=0;i<m;i++)
        for(int j=0;j<n;j++)
        if(forest[i][j]>1)trees.push_back({i,j});
        //对左边进行顺序化处理
        sort(trees.begin(), trees.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2)
        {
            return forest[p1.first][p1.second] < forest[p2.first][p2.second];
        });
        int dx=0,dy=0;
        int ret=0;
        for(auto [a,b]:trees)
        {
            int t=bfs(forest,dx,dy,a,b);//调用bfs得到到达目的地的步数
            if(t==-1)return -1;//无法到达就返回-1终止;
            ret+=t;//累加合法的步数
            dx=a;dy=b;//更新起点
        }
        return ret;
       

    }
    int _x[4]={0,0,1,-1};
    int _y[4]={1,-1,0,0};
    int bfs(vector<vector<int>>& forest,int dx,int dy,int ex,int ey)
    {
        if(dx==ex&&dy==ey)return 0;//如果开始就是终点直接返回
        bool vis[51][51];//标记数组
        memset(vis,0,sizeof vis);//每次从起点到终点都需要重置标记
        queue<pair<int,int>>q;
        q.push({dx,dy});
        vis[dx][dy]=true;//走过的就没必要进行遍历了
        int ret=0;
        while(q.size())
        {
            ret++;
            int sz=q.size();
            while(sz--)
            {
                //进行遍历
                auto [a,b]=q.front();
                q.pop();
                for(int i=0;i<4;i++)
                {
                    int x=a+_x[i];int y=b+_y[i];
                    if(x>=0&&x<m&&y>=0&&y<n&&forest[x][y]&&!vis[x][y])//位置合法且没有走过,树的高度合法
                    {
                        //如果达到目的地就返回
                        if(x==ex&&y==ey)return ret;
                        q.push({x,y});
                        vis[x][y]=true;
                    }
                }
            }
        }
        return -1;
    }
};

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

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

相关文章

用MobaXterm,TightVNC和secure SSH实现两台windows电脑之间的连接和通信

今天给大家分享一个非常有趣的技术&#xff0c;那便是如何使用MobaXterm来实现两台电脑之间的通信。实验成功&#xff0c;保证能跑。 首先&#xff0c;给大家介绍我们今天最重要的工具&#xff1a;那便是MobaXterm&#xff08;以下由ChatGPT生成&#xff09;&#xff1a; Moba…

C++初阶_2: inline内联函数 宏函数

C推出了inline关键字&#xff0c;其目的是为了替代C语言中的宏函数。 我们先来回顾宏函数&#xff1a; 宏函数 现有个需求&#xff1a;要求你写一个Add(x,y)的宏函数。 正确的写法有一种&#xff0c;错误的写法倒是五花八门&#xff0c;我们先来“见不贤而自省也。” // …

我怎么会这么依赖 GUI?

AWS CLI、.NET 和 Lambda 函数 欢迎来到雲闪世界。在 Windows 上使用 Visual Studio 和 AWS Explorer 绝对会让你变得懒惰。我的意思是&#xff0c;能够通过右键单击项目来构建和部署 Lambda 函数之类的东西真是太棒了&#xff0c;但有时最好了解幕后发生了什么。 尽管如此&am…

Java - 异常

异常处理&#xff1a; ①捕获异常 选中代码后ctrlAltt: try catch捕捉异常 ②抛出异常 加上throws 异常类型 自定义异常 Exception.java: package Exception;//自定义运行时异常 public class ExceptionTest {public static void main(String[] args) {//保存一个合法的年…

知识库管理软件购买指南:2024年十大选择

本篇文章介绍了以下工具&#xff1a;PingCode、Worktile、蓝湖、语雀、幕布、Guru、Helpjuice、Stack Overflow for Teams、KnowledgeOwl、eXo Platform。 在企业中&#xff0c;信息分散、难以获取是个常见的痛点。无论是新员工入职、团队协作&#xff0c;还是项目管理&#xf…

电脑高手必备!这款数据恢复神器你值得拥有

哎呀&#xff0c;现在我们天天在用的手机、电脑和各种移动设备都是有很多日常和工作中的重要数据&#xff0c;丢失重要的文件和数据的时候就很着急&#xff0c;所以我也很感同身受&#xff0c;为此困扰过我好几次&#xff0c;所以今天特地借此文章整理了常用的失易得数据恢复软…

Unity游戏开发002

Unity游戏开发002 目录 第一章&#xff1a;Hello&#xff0c;Unity&#xff01;第二章&#xff1a;创建一个游戏体 本文目录 Unity游戏开发 Unity游戏开发002目录本文目录前言一、创建一个游戏体1. 编辑器语言设置2. 创建游戏对象的两种方法3. 快速复制和粘贴物体4. 注意事项…

/springmvc/xxx.html和/springmvc/xxx的区别

起因&#xff1a; 今天在访问webapp目录下的html文件时&#xff0c;突然报了500错误&#xff08;如下图&#xff09;&#xff0c;让我检查视图解析器&#xff0c;我寻思我访问的是静态资源&#xff0c;不是直接交给servlet处理嘛&#xff1f;需要什么视图解析器&#xff1f;&a…

13.C基础_预处理

预处理语句就是以#开头的语句。这些语句类型如下&#xff1a; #include&#xff1a;包含头文件#define&#xff1a;宏定义#undef&#xff1a;取消宏定义#ifdef&#xff0c;#endif&#xff1a;成对使用&#xff0c;判断是否定义了某个宏 宏定义 宏定义的本质就是原样替换&…

【Material-UI】Floating Action Button (FAB) 详解:动画效果 (Animation)

文章目录 一、FAB 按钮的动画概述1. 默认动画效果2. 多屏幕横向切换时的动画 二、FAB 动画效果的实现1. 代码示例&#xff1a;跨标签页的 FAB 动画2. 代码解析3. 多个 FAB 的切换 三、动画效果的最佳实践四、总结 在现代网页设计中&#xff0c;动画不仅提升了用户界面的动态感&…

React+AntDesign做一个日历,展示节假日,节气,并且在某几个时间上添加活动备注

直接贴效果图😄 首先日历是用的AntDesign提供的Calendar组件,这个组件还是蛮强大的,可以自定义头部时间下拉;渲染每个时间段,或者重置时间段内容,玩的空间是很大的 直接贴代码,结尾最后我会将开发中遇到的问题贴出来解答一下 第一步:下载js-calendar-converter添加…

SpringBoot集成日志框架

SpringBoot集成日志框架 Java生态体系日志框架介绍 简介 在Java生态体系中&#xff0c;围绕着日志&#xff0c;有很多成熟的解决方案。关于日志输出&#xff0c;主要有两类工具。 一类是日志框架&#xff08;Log4j、Logback&#xff09;&#xff0c;主要用来进行日志的输出的…

Unity 使用 NewtonSoft Json插件报错

JsonReaderException: Unexpected character encountered while parsing value: . Path , line 0, position 0. 通过断点发现&#xff0c;头有一串ZWNBSP&#xff0c;这个是BOM格式的JSON。在文件下看不到。 解决方法&#xff1a;改编码格式&#xff0c;Remove BOM.

Linux信号的概念信号的产生

前言 我们前面已经对进程已做了介绍&#xff01;知道进程具有独立性&#xff0c;但在运行起来后可能会"放飞自我"&#xff0c;即不受控制的执行&#xff0c;这就会导致系统崩溃等问题&#xff0c;非常不利于管理。因此OS需要一种机制来协调和控制进程的运行&#xf…

【C++】拓扑排序(BFS)

目录 拓扑排序介绍 有向无环图 如何解决这类问题 课程表 算法思路 代码实现 课程表2 算法思路 代码实现 火星词典 代码实现 拓扑排序介绍 有向无环图 入度&#xff1a;指向活动节点的箭头个数&#xff1b; 出度&#xff1a;从活动节点出去指向别的节点的箭头个数。…

交互式实时距离测量-单目测距-社交距离检测

使用说明 使用鼠标点击两个目标框要删除在距离计算过程中绘制的点&#xff0c;你可以使用鼠标右键点击。这会清除所有已绘制的点 使用 Ultralytics YOLOv8 进行距离计算 距离计算是在指定空间内测量两个物体之间间隙的基本概念。在 Ultralytics YOLOv8 的情况下&#xff0c;通…

React学习-初始化react项目

目标: reactv18&#xff1a;->1.核心的22中api2路由3.数据状态管理&#xff1a;redux项目&#xff1a; 1.b端业务闭环:登录方案、权限设计、用户管理方案、业务功能、系统架构设计、路由设计流程闭环&#xff1a;开发环境、生产环境、测试环境、代码规范、分支管理规范、项…

SpringBoot整合knife4j配置使用直接拷贝即可(快速入门超详细版)

1. SpringBoor整合Knife4j添加maven 1.1 第一种maven <!--添加Knife4j依赖--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.5.0</ver…

Unity新输入系统 之 PlayerInput(真正的最后封装部分)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​ 首先你应该了解新输入系统的基本单位和输入配置文件 Unity新输入系统 之 InputAction&#xff08;输入配置文件最基本的…

6 款最佳付费和免费 iPhone 解锁应用和软件

iPhone解锁应用程序是一种可以不受任何限制地移除 iOS 设备上不同类型锁的工具。iPhone 可能受锁屏密码、Apple ID 密码、屏幕使用时间密码、iCloud 激活锁、MDM 等保护。如果您忘记了密码&#xff0c;您将无法使用设备或无法完全访问您的 iPhone。幸运的是&#xff0c;有软件可…