BFS解决最短路问题(详解)
目录
BFS简介 && 框架:
一.二叉树的最小深度
二:迷宫中里入口最近的出口:
三.最小基因变化:
四:单词接龙:
五:为高尔夫比赛砍树:
BFS简介 && 框架:
说到BFS,想必大家都不陌生,我们在学习二叉树的遍历时所用到的层序遍历其实就是BFS,那么BFS能解决什么样的问题呢?通过本文标题,你也大致能猜到----解决最短路问题,这是一个比较经典的问题,当然,BFS还能解决很多其他问题,本文就着重介绍其中一种---最短路问题。
BFS基本概念:
- BFS(广度优先搜索):是一种图形搜索算法,它从根节点开始,逐层地向下扩展搜索。BFS通常用队列来实现,即先进先出的数据结构。这意味着每个节点都将按照它们被发现的顺序进行处理。具体地说,BFS算法会首先访问根节点,然后将其所有相邻的节点加入队列中。接下来,它将按照与队列中节点相同的顺序访问这些新节点,并将它们的相邻节点加入队列中。该过程一直持续到所有节点都被访问为止。
我们先举例一下 BFS 出现的常见场景吧,问题的本质就是让你在一幅「图」中找到从起点 start 到终点 target 的最近距离,这个例子听起来很枯燥,但是 BFS 算法问题其实都是在干这个事儿 ,其对应的大致框架如下:
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {Queue<Node> q; // 核心数据结构Set<Node> visited; // 避免走回头路q.offer(start); // 将起点加入队列visited.add(start);while (q not empty) {int sz = q.size();/* 将当前队列中的所有节点向四周扩散 */for (int i = 0; i < sz; i++) {Node cur = q.poll();/* 划重点:这里判断是否到达终点 */if (cur is target)return step;/* 将 cur 的相邻节点加入队列 */for (Node x : cur.adj()) {if (x not in visited) {q.offer(x);visited.add(x);}}}}// 如果走到这里,说明在图中没有找到目标节点
}
一.二叉树的最小深度
先来个简单的问题实践一下 BFS 框架吧,判断一棵二叉树的最小高度,这也是力扣第 111 题「二叉树的最小深度open in new window」:
题目描述:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
这题比较简单,直接套用上面的框架即可,遇到第一个叶子节点直接返回对应深度即可:
代码详解:
class Solution {public int minDepth(TreeNode root) {Queue<TreeNode> q = new LinkedList<>();if(root == null){return 0;}int depth = 1;//初始树的深度是1q.offer(root);while(!q.isEmpty()){int sz = q.size();for(int i = 0;i < sz;i++){TreeNode cur = q.poll();if(cur.left == null && cur.right == null){return depth;}//----->如果cur == null --->// cur.left不存在,null指针异常/* q.offer(cur.left);q.offer(cur.right);不能直接将节点放入队列,*/if(cur.left != null){//要判空q.offer(cur.left);}if(cur.right != null){q.offer(cur.right);}}depth++;}return depth;}
}
运行结果:
这里注意这个 while 循环和 for 循环的配合,while 循环控制一层一层往下走,for 循环利用 sz 变量控制从左到右遍历每一层二叉树节点,将节点加入到队列中:

二:迷宫中里入口最近的出口:
题目链接:1926. 迷宫中离入口最近的出口 - 力扣(LeetCode)
题目描述:
给你一个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格子(用 '.' 表示)和墙(用 '+' 表示)。同时给你迷宫的入口 entrance ,用 entrance = [entrancerow, entrancecol] 表示你一开始所在格子的行和列。
每一步操作,你可以往 上,下,左 或者 右 移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance 最近 的出口。出口 的含义是 maze 边界 上的 空格子。entrance 格子 不算 出口。
请你返回从 entrance 到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1 。

思路:我们可以从起点开始层序遍历,并且在遍历的过程中记录当前遍历的层数。这样就能在找到出⼝的 时候,得到起点到出⼝的最短距离
其中,为了方便表示四个方向,我们可以通过定义两个数组来表示方向:dx[ ],dy[ ],其中dx[ ],dy[ ]的位置要一一对应,具体操作如下:

代码详解:
class Solution {int[] dx = {0,0,-1,1};int[] dy = {1,-1,0,0};boolean[][] used;public int nearestExit(char[][] maze, int[] e) {int m = maze.length,n = maze[0].length;used = new boolean[m][n];//HashSet -- > Set来记录路径(走过的就不要再去走了)Queue<int[]> q = new LinkedList<>(); //int[]用来记录maze的下标q.offer(new int[]{e[0],e[1]});used[e[0]][e[1]] = true;int step = 0;//初始步数是0while(!q.isEmpty()){int sz = q.size();step++;for(int i = 0;i < sz;i++){int[] arr = q.poll();int a = arr[0],b = arr[1];for(int k = 0;k < 4;k++){//四个方向int x = dx[k] + a,y = dy[k] + b;if(x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == '.' && !used[x][y]){if(x == 0 || x == m - 1 || y == 0 || y == n - 1){return step;}q.offer(new int[]{x,y});used[x][y] = true;}}}}return -1;//找不到就返回-1}
}
运行结果:

三.最小基因变化:
题目链接:433. 最小基因变化 - 力扣(LeetCode)
题目描述:
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'、'C'、'G' 和 'T' 之一。
假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
- 例如,
"AACCGGTT" --> "AACCGGTA"就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)
给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。
注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

思路:用哈希表来记录搜索过的状态,存储基因库的信息(后续O(1)的时间查找),遍历一个字符串,将每个位置的可能的四种情况全部枚举出来,将没有被搜过或者存在基因库中的节点加入到队列。
代码详解:
class Solution {public int minMutation(String startGene, String endGene, String[] bank) {Set<String> hash = new HashSet<>();Set<String> vis = new HashSet<>();for(String x : bank) hash.add(x);char[] change = {'A','C','G','T'};if(!hash.contains(endGene)) return -1;if(endGene.equals(startGene)) return 0;Queue<String> q = new LinkedList<>();q.offer(startGene);vis.add(startGene);int step = 0;while(!q.isEmpty()){step++;int sz = q.size();while(sz-- != 0){String cur = q.poll();for(int i = 0;i < 8;i++){char[] temp = cur.toCharArray();for(int j = 0;j < 4;j++){temp[i] = change[j];String next = new String(temp);if(!vis.contains(next) && hash.contains(next)){if(next.equals(endGene)) return step;q.offer(next);vis.add(next);}}}}}return -1;}
}
运行结果:

四:单词接龙:
题目链接:127. 单词接龙 - 力扣(LeetCode)
题目描述:
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k时,每个si都在wordList中。注意,beginWord不需要在wordList中。 sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

与上面一题基本类似,直接看代码:
class Solution {public int ladderLength(String beginWord, String endWord, List<String> wordList) { Set<String> hash = new HashSet<>();//用于记录单词库中的单词Set<String> vis = new HashSet<>();//用于记录路径for(String x : wordList) hash.add(x);char[] change = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};//记录要该表的列表//处理一下边界条件//if(beginWord.equals(endWord)) return 1; -->题目给出不会相等if(!hash.contains(endWord)) return 0;//bfsQueue<String> q = new LinkedList<>();q.offer(beginWord);vis.add(beginWord);int step = 1;//用于记录单词数while(!q.isEmpty()){step++;int sz = q.size();while(sz-- != 0){String cur = q.poll();int k = cur.length();for(int i = 0;i < k;i++){char[] temp = cur.toCharArray();for(int j = 0;j < 26;j++){temp[i] = change[j];String next = new String(temp);//满足条件入队列if(!vis.contains(next) && hash.contains(next)){if(next.equals(endWord)) return step;//找到直接返回单词数q.offer(next);vis.add(next);}}}}}return 0;}
}
运行结果:
五:为高尔夫比赛砍树:
题目链接:675. 为高尔夫比赛砍树 - 力扣(LeetCode)
题目描述:
你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n 的矩阵表示, 在这个矩阵中:
0表示障碍,无法触碰1表示地面,可以行走比 1 大的数表示有树的单元格,可以行走,数值表示树的高度
每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。
你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1(即变为地面)。
你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。
可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。

思路:
a. 先找出砍树的顺序;
b. 然后按照砍树的顺序,⼀个⼀个的⽤ bfs 求出最短路即可。
代码详解:
class Solution {int m,n;public int cutOffTree(List<List<Integer>> forest) {m = forest.size();n = forest.get(0).size();//将是树的位置装入容器中List<int[]> trees = new ArrayList<>();for(int i = 0;i < m;i++){for(int j = 0;j < n;j++){if(forest.get(i).get(j) > 1){trees.add(new int[]{i,j});}}}//排序,按照树的相对高度对树对应坐标排序Collections.sort(trees,(a,b) ->{return forest.get(a[0]).get(a[1]) - forest.get(b[0]).get(b[1]);});int res = 0;int bx = 0,by = 0;for(int[] tree : trees){int x = tree[0],y = tree[1];//拿出下一个要去的位置的下表int step = bfs(forest,bx,by,x,y);//迷宫问题if(step == -1) return -1;res += step;//所有路径的最小值的和,就是答案bx = x;by = y;//更新起始位置的值}return res;}//迷宫问题->找到目标位置的最小步数int[] dx = {0,0,-1,1};int[] dy = {1,-1,0,0};public int bfs(List<List<Integer>> forest,int bx,int by,int ex,int ey){if(bx == ex && by == ey) return 0;//如果目标就在原地,直接返回boolean[][] used = new boolean[m][n];Queue<int[]> q = new LinkedList<>();q.offer(new int[]{bx,by});used[bx][by] = true;int step = 0;while(!q.isEmpty()){step++;int sz = q.size();while(sz-- != 0){int[] temp = q.poll();int a = temp[0],b = temp[1];for(int i = 0;i < 4;i++){int x = a + dx[i],y = b + dy[i];if(x >= 0 && x < m && y >= 0 && y < n && !used[x][y] && forest.get(x).get(y) != 0){if(x == ex && y == ey) return step;q.offer(new int[]{x,y});used[x][y] = true;}}}}return -1;//没找到就返回-1}
}
结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!

相关文章:
BFS解决最短路问题(详解)
目录 BFS简介 && 框架: 一.二叉树的最小深度 二:迷宫中里入口最近的出口: 三.最小基因变化: 四:单词接龙: 五:为高尔夫比赛砍树: BFS简介 && 框架: 说到BFS…...
按尺寸筛选轮廓图中的轮廓
1.按短边筛选 原始轮廓图: import cv2 import numpy as np# 读取轮廓图 contour_image cv2.imread(..\\IMGS\\pp_edge.png, cv2.IMREAD_GRAYSCALE)# 使用cv2.findContours()函数获取所有轮廓 contours, _ cv2.findContours(contour_image, cv2.RETR_EXTERNAL, cv2…...
VBA高级应用30例:实现在列表框内及列表框间实现数据拖动
《VBA高级应用30例》(版权10178985),是我推出的第十套教程,教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开,这套教程案例与理论结合,紧贴“实战”,并做“战术总结”,以…...
「AIGC算法」R-tree算法
R-tree算法是一种非常实用的空间数据索引技术,它可以帮助我们在复杂的空间数据中快速找到我们想要的信息。下面我将用一些生活中的例子来帮助大家更好地理解R-tree算法。 1. 定义与原理 想象一下,你有一个巨大的图书馆,里面有成千上万本书,每本书都有它在书架上的特定位置…...
2024软考上半年嵌入式系统设计师考试回顾
一:考试准备工作 1:基本上都是提前30分钟进考场,进入考试教室的时候,会有监考老师核对身份证和准考证; 2:进入考试教室之后,会再一次核对身份信息,并且有监考老师手持扫描仪&#x…...
MIT6.828 Lab2-1 Using gdb
Using gdb gdb使用: xv6 gdb调试方法 问题1: Looking at the backtrace output, which function called syscall? 按照提示开启gdb后键入: b syscall c layout src backtrace输出结果: (gdb) backtrace #0 syscall () at k…...
mysqldump提示Using a password on the command line interface can be insecured的解决办法
mysql数据库备份一句话执行命令 mysqldump --all-databases -h127.0.0.1 -uroot -p123456 > allbackupfile.sql 提示如下提示 [rootyfvyy5b2on3knb8q opt]# mysqldump --all-databases -h127.0.0.1 > allbackupfile.sql mysqldump: Couldnt execute SELECT COLUMN_NA…...
Java毕业设计 基于springboot vue考勤管理系统
Java毕业设计 基于springboot vue考勤管理系统 SpringBoot 考勤管理系统 功能介绍 员工 登录 个人中心 修改密码 个人信息 员工请假管理 员工出差管理 薪资管理 员工签到管理 公告管理 管理员 登录 个人中心 修改密码 个人信息 员工管理 员工请假管理 员工出差管理 薪资管理…...
C数据结构:二叉树
目录 二叉树的数据结构 前序遍历 中序遍历 后序遍历 二叉树的创建 二叉树的销毁 二叉树的节点个数 二叉树叶子节点个数 二叉树第K层节点个数 二叉树的查找 层序遍历 判断二叉树是否为完全二叉树 完整代码 二叉树的数据结构 typedef char BTDataType; typedef str…...
使用Nginx作为反向代理实现MQTT内外网通信
使用Nginx作为反向代理实现MQTT内外网通信 步骤1: 安装Nginx 确保你的服务器上已安装Nginx。如果未安装,可以通过以下命令在Ubuntu上安装Nginx: sudo apt update sudo apt install nginx步骤2: 配置Nginx 编辑Nginx的配置文件,通常是/etc…...
SpringBoot 上传文件示例
示例效果: 前端代码: <html> <head><title>上传文件示例</title></head> <body> <h2>方式一:普通表单上传</h2> <form action"/admin/upload" method"post" enctyp…...
9.js函数
函数是js复杂数据类型的一种---可以理解为存放代码的盒子 用来帮助我们封装、复用、扩展以及调用代码的工具 函数的两个阶段 (1)声明函数(理解为创造) ——声明式声明 语法:function 函数名(参数){...代码} ——赋值时…...
关于数据库和数据表的基础SQL
目录 一. 数据库的基础SQL 1. 创建数据库 2. 查看当前有哪些数据库 3. 选中数据库 4. 删除数据库 5. 小结 二. 数据表的基础SQL 1. 创建数据表 2. 查看当前数据库中有哪些表 3. 查看指定表的详细情况(查看表的结构) 4. 删除表 5. 小结 一. 数据库的基础SQL 1. 创建…...
【C语言深度解剖】(14):结构体内存对齐(详细配图讲解)
🤡博客主页:醉竺 🥰本文专栏:《C语言深度解剖》 😻欢迎关注:感谢大家的点赞评论关注,祝您学有所成! ✨✨💜💛想要学习更多C语言深度解剖点击专栏链接查看&…...
学习笔记:C语言的32个关键字
一、标准C语言的32个关键字 1、基本数据类型: signed unsigned char int float double short long void 2、构造数据类型: struct union enum 3、数据存储类别: auto static extern register 4、数据优化: const volatile 5、9条…...
嵌入式学习 (Day:27 IPC --- 进程间通信)
IPC 进程间通信 interprocess communicate (即:进程间进行数据交换) 三大类: 进程间通信的方式(共8种) 1、古老的通信方式(Linux设计时就有的) 无名管道 有名…...
Python考试复习--day2
1.出租车计费 mile,waitmap(int,input().split(,)) if mile<3:money13wait*1 elif mile>3 and mile<15:money13(mile-3)*2.3wait*1 else:money1312*2.3(mile-15)*2.3*(10.5)wait*1 print({:.0f}.format(money)) 【知识点1】: map() 函数 【知识点1】&…...
整理好了!2024年最常见 20 道 Redis面试题(九)
上一篇地址:整理好了!2024年最常见 20 道 Redis面试题(八)-CSDN博客 十七、Redis 的过期策略有哪些? Redis 的过期策略主要有三种: 定时删除:当为一个键设置了过期时间后,Redis 会…...
IDEA使用Maven打包项目的所有的依赖
要使用 Maven 命令将 Spring Boot 项目的依赖打包到 lib 文件夹中,你可以在终端中运行以下命令: mvn dependency:copy-dependencies -DoutputDirectory./lib这个命令会将项目的所有依赖(包括运行时依赖)复制到当前目录的 lib 文件…...
【C++ 】学习问题及补充
一.自定义类型不初始化直接就赋值,比如string类会怎么样 vectr<string>里已经给每个string对象已经分配好空间,为什么不初始化再赋值会报错 在C中,std::string类是一个动态字符串类,它内部管理着一个字符数组,用…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
