BFS 解决最短路问题

目录

一、前言

1.1 如何使用 BFS 找到最短路:

1.2 为什么不用 dfs :

二、模板套路

三、例题练习

3.1 例题1:迷宫中离入口最近的出口

3.2 例题2:最小基因变化

3.3 例题3:单词接龙

3.4 例题4:为高尔夫比赛砍树


一、前言

最短路问题一般我们都是在图论中会遇到的问题,对应的算法有 Dijkstra 算法,Bellman-Ford 算法,Floyd-Warshall 算法。上面的三个算法后续在图论的文章里面会单独再介绍(内容比较多),今天主要介绍使用 BFS 来解决边权为 1 的最短路问题,边权为 1 的这个条件可以衍生为边权全部相同。为了方便叙述下面所有的最短路问题都是边权全部相同的情况。

1.1 如何使用 BFS 找到最短路:

解法:从起点开始,来一次 BFS 即可。扩展的层数就是最短路的长度,一旦遍历到终点立即返回对应的层数,就是最短路的长度。对应过程如下图 BFS 就是对应每条路径(不同颜色)同时在周围扩散一步。

1.2 为什么不用 dfs :

使用 dfs 大概率会超时,因为:bfs 不用遍历所有节点,找到直接返回就是最小值。而 dfs 必须要把全部路径都找一遍才能找到最小值,时间复杂度是比较高的,所以这类问题我们一般使用 bfs 来解决。

二、模板套路

• 参数解释:

map:对应查找数组。

sr | sc:起点坐标。

er | ec:终点坐标。

path:记录路径长度。

vis:去重。

public int bfs(char[][] map,int sr,int sc,int er,int ec){Queue<int[]> queue = new LinkedList<>();queue.offer(new int[]{sr,sc});//先放入起点int path = 0;//记录路径长度while(!queue.isEmpty()){int size = queue.size();path++;//向外扩展一层for(int i = 0;i < size;i++){int[] tmp = queue.poll();int a = tmp[0],b = tmp[1];vis[a][b] = true;for(int k = 0;k < 4;k++){int x = a + dx[k];int y = b + dy[k];if(x >= 0 && x < n && y >= 0 && y < m && 题目对应条件 &&!vis[x][y]){if(达到出口条件){return path;//返回}queue.offer(new int[]{x,y});vis[x][y] = true;}}}}return -1;//没找到的情况,具体返回什么看题目}

上面就是大体的框架,默认起点不会是终点(题目要求可以的话,来个特判即可),如果对 BFS 不是很熟悉的话可以结合 BFS解决FloodFIll算法 来学习。

三、例题练习

3.1 例题1:迷宫中离入口最近的出口

• 题目链接:迷宫中离入口最近的出口

• 问题描述:

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

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

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

• 解题思路:

利用 BFS 来解决是这类题目最经典(边权为 1 的最短路问题)的做法。从起点开始 BFS ,用 path 来记录当前遍历的层数,这样就能在找到出口的时候,返回起点到出口的最短长度。基本就是套模板即可,不同的是终点是在边界地方而不是作为 bfs 参数。

• 代码编写:

class Solution {int n,m;boolean[][] vis;int[] dx = {0,0,1,-1};int[] dy = {1,-1,0,0};public int nearestExit(char[][] maze, int[] entrance) {n = maze.length;m = maze[0].length;vis = new boolean[n][m];int ans = bfs(maze,entrance[0],entrance[1]);return ans;}public int bfs(char[][] map,int sr,int sc){Queue<int[]> queue = new LinkedList<>();queue.offer(new int[]{sr,sc});int path = 0;while(!queue.isEmpty()){int size = queue.size();path++;//向外扩展一层for(int t = 0;t < size;t++){int[] tmp = queue.poll();int a = tmp[0],b = tmp[1];vis[a][b] = true;for(int k = 0;k < 4;k++){int x = a + dx[k];int y = b + dy[k];if(x >= 0 && x < n && y >= 0 && y < m && map[x][y] == '.' && !vis[x][y]){if(x == 0 || x == n - 1 || y == 0 || y == m - 1){return path;}queue.offer(new int[]{x,y});vis[x][y] = true;}}}}return -1;}
}

3.2 例题2:最小基因变化

• 题目链接:最小基因变化

• 问题描述:

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

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

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

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

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

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

• 解题思路:

首先因为字符变化并没有权重,所以这是边权为 1 的最短路问题。那么如何枚举出所有的变化情况呢?答:暴力,因为题目的数据都很小,我们可以把字符串每个位置的字符都用 'A','C','G','T'来替换看看在不在基因库中存在,如果存在且没有被找过,存入到队列中。我们可以使用语言带的哈希表来标记搜索过的地方。

优化:我们预先处理基因库,把基因库里面的数据存入到哈希表中,这样就可以用O(1)的时间复杂度来快速找到。

• 代码编写:

class Solution {public int minMutation(String startGene, String endGene, String[] bank) {// BFS// 1.创建 哈希表 来快速判断Set<String> hash = new HashSet<>();// 用来快速判断一个字符串是否再bank里面出现Set<String> vis = new HashSet<>();// 用来标记已经变化过的字符串char[] change = { 'A', 'C', 'G', 'T' };for (String tmp : bank) {hash.add(tmp);}if (startGene.equals(endGene)) {// 处理边界情况return 0;}if (!hash.contains(endGene)) {return -1;}Queue<String> queue = new LinkedList<>();queue.offer(startGene);//存入开始位置int path = 0;while (!queue.isEmpty()) {path++;// 代表剥离一层int size = queue.size();for (int i = 0; i < size; i++) {// 找出全部变化String tmp = queue.poll();vis.add(tmp);for (int j = 0; j < 8; j++) {// 把8个位置上的元素全部修改char[] s = tmp.toCharArray();//细节问题,不能放在外面,因为放在//外面那么就不能保证只修改一个元素了for (int k = 0; k < 4; k++) {s[j] = change[k];String next = new String(s);//char[]是不能toString的if (next.equals(endGene)) {//找到出口return path;}if (hash.contains(next) && !vis.contains(next)) {queue.offer(next);vis.add(next);}}}}}return -1;}
}

3.3 例题3:单词接龙

• 题目链接:单词接龙

• 问题描述:

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

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

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

• 解题思路:

这题可以说是例题2的升级版,因此基本解法是一样的,区别就是例题2是 4 个字符替换,本题是26个字符替换。注意这题找不到是返回 0。

优化:如果 endWord 不在 wordList 中直接返回 0 即可。

• 代码编写:

class Solution {public int ladderLength(String beginWord, String endWord, List<String> wordList) {//最短路径问题,权值是相同的//采用 BFS 来解决char[] change = new char[26];for(int i = 0;i < 26;i++){change[i] = (char)(i + 'a');}Set<String> hash = new HashSet<>();//用来记录字典Set<String> set = new HashSet<>();//用来去重//处理一些边界情况//题目说明了不会出现这种情况for(String s:wordList){hash.add(s);}if(!hash.contains(endWord)){//不存在的情况return 0;}Queue<String> queue = new LinkedList<>();queue.offer(beginWord);int n = beginWord.length();//每个单词有多长//进行 BFS 查找int path = 1;//用来记录层数while(!queue.isEmpty()){int size = queue.size();path++;for(int i = 0;i < size;i++){String next = queue.poll();set.add(next);for(int j = 0;j < n;j++){char[] s = next.toCharArray();//方便替换for(int k = 0;k < 26;k++){s[j] = change[k];String tmp = new String(s);if(tmp.equals(endWord)){//找到答案return path;}if(hash.contains(tmp) && !set.contains(tmp)){queue.offer(tmp);set.add(tmp);//标记为找到了}}}}} return 0;//注意这题找不到是返回0}
}

3.4 例题4:为高尔夫比赛砍树

• 题目链接:为高尔夫比赛砍树

• 问题描述:

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

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

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

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

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

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

• 解题思路:

1. 找出砍树的顺序。

2. 按照砍树的顺序,一个一个的用 bfs 求出最短路即可(封装成一个函数)。

• 代码编写:

直接利用语言自带 sort 排序,注意每次传入 bfs 求最短路的起点和终点每次都不一样,要一直更新。

class Solution {int n,m;public int cutOffTree(List<List<Integer>> forest) {n = forest.size();m = forest.get(0).size();//1.把不为0的数的下标存入listList<int[]> ret = new ArrayList<>();for(int i = 0;i < n;i++){for(int j = 0;j < m;j++){if(forest.get(i).get(j) > 1){ret.add(new int[]{i,j});}}}//2.排序Collections.sort(ret,(o1,o2) -> {return forest.get(o1[0]).get(o1[1]) > forest.get(o2[0]).get(o2[1]) ? 1 : -1;});//从小到大排序//3.从小到大用dfs找,找不到返回-1int path = 0;int sum = 0;//最后全部的和int x = 0,y = 0;//起点for(int[] tmp:ret){path = bfs(forest,x,y,tmp[0],tmp[1]);if(path == -1){return -1;//找不到立即返回 -1}x = tmp[0];y = tmp[1];//每个起点是不一样的,要一直更新sum += path;}return sum;}int[] dx = {0,0,1,-1};int[] dy = {1,-1,0,0};public int bfs(List<List<Integer>> f, int bx, int by, int ex, int ey){if(bx == ex && by == ey) return 0;//可能起点即终点Queue<int[]> q = new LinkedList<>();boolean[][] vis = new boolean[n][m];q.add(new int[]{bx, by});vis[bx][by] = true;int step = 0;while(!q.isEmpty()){int sz = q.size();step++;while(sz-- != 0){int[] t = q.poll();int a = t[0], b = t[1];for(int i = 0; i < 4; i++){int x = a + dx[i], y = b + dy[i];if(x >= 0 && x < n && y >= 0 && y < m && f.get(x).get(y)!= 0 && !vis[x][y]){if(x == ex && y == ey) return step;q.add(new int[]{x, y});vis[x][y] = true;}}}}return -1;}
}

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

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

相关文章

HTML橙色爱心

目录 写在前面 准备开始 完整代码 运行结果 系列文章 写在后面 写在前面 本期小编给大家分享一颗热烈且浪漫的爱心&#xff0c;快来看看吧&#xff01; 准备开始 在开始之前&#xff0c;我们需要先简单的了解一下这颗爱心的原理哦~ 本期将用html实现这颗跳动的爱心&a…

YOLOv9改进策略 | 图像去雾 | 利用图像去雾网络UnfogNet辅助YOLOv9进行图像去雾检测(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用UnfogNet超轻量化图像去雾网络,我将该网络结合YOLOv9针对图像进行去雾检测(也适用于一些模糊场景),我将该网络结构和YOLOv9的网络进行结合同时该网络的结构的参数量非常的小,我们将其添加到模型里增加的计算量和参数量基本可…

跨平台之用VisualStudio开发APK嵌入OpenCV(二)

开始干 新建解决方案&#xff0c;新建动态库&#xff08;Android&#xff09;项目 功能随便选一个吧&#xff0c;就模仿PS&#xff08;Photoshop&#xff09;的透视裁切功能&#xff0c;一个物体&#xff08;比如扑克牌&#xff09;透视图&#xff0c;选4个顶点&#xff0c;转…

python文件处理之os模块和shutil模块

目录 1.os模块 os.path.exists(path)&#xff1a;文件或者目录存在与否判断 os.path.isfile(path)&#xff1a;判断是否是文件 os.path.isdir(path)&#xff1a;判断是否是文件夹 os.remove(path)&#xff1a;尝试删除文件 os.rmdir(path)&#xff1a;尝试删除目录 os.m…

vue项目elementui刷新页面弹窗问题

bug&#xff1a;每次刷新页面都有这个鬼弹窗。 刚开始以为是自己的代码问题&#xff0c;于是我翻遍了每一行代码&#xff0c;硬是没找出问题。 后来在网上找了些资料&#xff0c;原来是引入的问题。 解决方案&#xff1a; 改一下引入方式即可。 错误姿势 import Vue from …

美发店服务预约会员小程序的作用是什么

美发店不同于美容美甲&#xff0c;男女都是必需且年龄层几乎不限&#xff0c;商家在市场拓展时只要方法得当相对比较容易&#xff0c;当今客户适应于线上信息获取、咨询及实际内容开展&#xff0c;商家也需要赋能和提升自身服务效率&#xff0c;合理化管理。 运用【雨科】平台…

2024年【高压电工】新版试题及高压电工找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 高压电工新版试题是安全生产模拟考试一点通生成的&#xff0c;高压电工证模拟考试题库是根据高压电工最新版教材汇编出高压电工仿真模拟考试。2024年【高压电工】新版试题及高压电工找解析 1、【单选题】 110KV及以下…

文件批量重命名利器:一键轻松替换文本间内容,高效管理文件不再是难题!

在信息爆炸的时代&#xff0c;我们的电脑中堆积了无数的文件。这些文件可能包含重要的工作资料、珍贵的个人回忆或是各种学习资料。然而&#xff0c;随着文件的不断增多&#xff0c;如何高效地管理和查找这些文件成为了一个头疼的问题。 文件批量改名高手是一款专业的文件管理…

在IDEA中配置servlet(maven配置完成的基础下)

在IDEA中配置servlet&#xff08;maven配置完成的基础下&#xff09; 1.先新建一个项目 2.选择尾巴是webapp的&#xff0c;名称自定义 3.点击高级设置&#xff0c;修改组id 点击创建&#xff0c;等待jar包下载完成。在pom.xml中配置以下 <dependency><groupId>ja…

docker同步bilibili收藏视频到群晖,可配合emby

作者是amtoaer&#xff0c;在github项目地址&#xff1a;https://github.com/amtoaer/bili-sync 有两个版本&#xff0c;1.0和2.0&#xff0c;我使用的是2.0 PS2&#xff1a;2.0和1.0版本目录结构不兼容&#xff0c;所以部署后会全量重新下载视频。 演示&#xff1a; 依然是…

OpenH264 编解码器介绍

思科 思科系统&#xff08;英语&#xff1a;Cisco Systems, Inc.&#xff09;是一间跨国际综合技术企业&#xff0c;总部设于加州硅谷&#xff1b;思科开发、制作和售卖网络硬件、软件、通信设备等高科技产品及服务&#xff0c;并透过子公司&#xff08;例子有OpenDNS、Webex、…

国赛练习(1)

Unzip 软连接 软连接是linux中一个常用命令&#xff0c;它的功能是为某一个文件在另外一个位置建立一个同步的链接。换句话说&#xff0c;也可以理解成Windows中的快捷方式 注意&#xff1a;在创建软连接的文件的所有目录下不能有重名的文件 打开环境&#xff0c;是文件上传&am…

用实践结果告诉你为啥说 CloudFlare 是赛博菩萨?

最近几天明月都没有更新博客了,主要是接了几个 CloudFlare 代维配置的活儿,有需要加速优化的,有需要排除疑难故障的,有需要提高防御攻击能力的甚至还有纯粹为了体验“打不死”装逼需要的。总之,各种各样的需求,五花八门的,好在 CloudFlare 都能一一满足,最主要的是这些…

Dockerfile使用

1.Dockerfile是什么 官网地址 https://docs.docker.com/reference/dockerfile/概念 是什么 Dockerfile 是用于构建 Docker 镜像的文本文件&#xff0c;它包含一系列的指令&#xff08;instructions&#xff09;和参数&#xff0c;用于描述如何构建和配置镜像。 Dockerfile 是…

解析售后维修服务平台如何助力企业高效运营与决策

随着生活质量的不断提高&#xff0c;人们对于售后服务的要求也越来越多。因此&#xff0c;售后服务已经成为企业竞争力的重要组成部分。售后服务平台作为连接企业与消费者的桥梁&#xff0c;不仅关乎着消费者的满意度&#xff0c;而且直接影响着企业的品牌形象与市场地位。那么…

[7] CUDA之常量内存与纹理内存

CUDA之常量内存与纹理内存 1. 常量内存 NVIDIA GPU卡从逻辑上对用户提供了 64KB 的常量内存空间&#xff0c;可以用来存储内核执行期间所需要的恒定数据常量内存对一些特定情况下的小数据量的访问具有相比全局内存的额外优势&#xff0c;使用常量内存也一定程序上减少了对全局…

使用python对指定文件夹下的pdf文件进行合并

使用python对指定文件夹下的pdf文件进行合并 介绍效果代码 介绍 对指定文件夹下的所有pdf文件进行合并成一个pdf文件。 效果 要合并的pdf文件&#xff0c;共计16个1页的pdf文件。 合并成功的pdf文件&#xff1a;一个16页的pdf文件。 代码 import os from PyPDF2 import …

深入理解 Spring Web 应用程序初始化流程

前言 在构建基于 Spring 的 Web 应用程序时&#xff0c;了解初始化流程是至关重要的。本文将详细介绍 Servlet 容器的初始化过程&#xff0c;并重点探讨 Spring 框架在其中的作用&#xff0c;特别是 ServletContainerInitializer、SpringServletContainerInitializer 和 WebAp…

源码部署ELK

目录 资源列表 基础环境 关闭防护墙 关闭内核安全机制 修改主机名 添加hosts映射 一、部署elasticsearch 修改limit限制 部署elasticsearch 修改配置文件 单节点 集群(3台节点集群为例) 启动 二、部署logstash 部署logstash 添加配置文件 启动 三、部署kiban…