当前位置: 首页 > news >正文

【BFS算法】广度搜索·由起点开始逐层向周围扩散求得最短路径(算法框架+题目)

0、前言

深度优先搜索是DFS(Depth Frst Search),其实就是前面所讲过的回溯算法,它的特点和它的名字一样,首先在一条路径上不断往下(深度)遍历,获得答案之后再返回,再继续往下遍历。这也是递归的思想,所以这也是为什么回溯算法通常都是用递归来写,而下面的BFS由于不是这种思路从而没有用递归。

广度优先算法(Breath First Search)其实和深度优先算法是一对兄弟,因为它们的解空间都是树形解空间,并且都是在求解过程中 动态生成树形解空间。广度优先算法在于,它在动态生成解空间从而获得答案时,是从一层一层(广度的)取构建并搜索答案。
在这里插入图片描述
它的核心思想是:把问题的求解抽象为一个图,从一个起点开始,向四周去扩散,不断扩散直至求得答案。

BFS和DFS的主要区别在于:

  • 在查找路径问题上,BFS查找出来的路径是最短的
  • BFS的空间复杂度要比DFS高(这也是BFS的代价)

一、算法框架

知道它的核心思想之后,其实我们就可以写出它的算法框架,本质就是遍历图

//计算起点start到终点target的最短距离
int BFS(Node start, Node target) {queue<Node> q; //核心数据结构set<Node> visited; //避免走回头路q.push(start);  //将起点加入队列visited.insert(start);//走完一次完整的下面1,2,3循环,代表以当前层节点完整的依次向外扩散了一层//循环1:从起点开始,不断将当前队列中所有节点向四周扩散while (!q.empty()) {int sz = q.size();//2:循环2:队列中所有节点依次往外扩散一层for (int i = 0; i < sz; i++) {Node cur = q.front();q.pop();if (cur == target)return step;//循环3:把当前层的队列节点往外扩散一层,并把扩散得到的加入队列for (Node x : cur.adj()) {if (visited.count(x) == 0) {q.push(x);visited.insert(x);}}}}// 如果走到这里,说明在图中没有找到目标节点
}
  • 其中Queue<Node> q队列,存储当前遍历层和即将遍历层的节点,是BFS的核心数据结构;
  • cur.adj()泛指与当前遍历到的这个节点cur相邻的节点(也就是与cur相连的下一层的节点,不如二维数组中cur上下左右四个位置就是相邻节点);
  • visited的作用防止走回头路;(但是对于二叉树这种结构,没有子节点到父节点的指针,不会走回头路也就不需要visited)
    在这里插入图片描述

二、题目

2.1:二叉树的最小高度

1.题目

🔗111. 二叉树的最小深度

在这里插入图片描述

2.思路

这道题就是非常典型和简单的BFS算法,我们只需要把上面算法框架中的cur == target找到目标答案的位置改成找到叶子节点:cur.left == null && cur.right == null即可

3.代码

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int minDepth(TreeNode* root) {if(root == nullptr) return 0;queue<TreeNode*> q; //核心数据结构,用来存储当前节点int depth = 0; //记录当前层的深度//将第一层输入队列:即起点q.push(root);//从当前层逐层扩散和遍历:循环1:遍历层while(!q.empty()){//获取当前层所以节点数量int sz = q.size();depth++;//循环2:遍历当前层的所有节点for(int i = 0; i < sz; i++){TreeNode* cur = q.front();q.pop();//判断当前遍历到的这个节点释放符合答案需求if(cur->left == nullptr && cur->right == nullptr)return depth;//循环3:以当前层的当前节点往外再扩一层,并把扩散得到的下一层节点加入队列//这里就已经确定了是左右孩子两个相邻节点,就不写循环了if(cur->left!=nullptr) q.push(cur->left);if(cur->right!=nullptr) q.push(cur->right);}}return 0;}
};

2.2:解开密码锁的最少次数

1.题目

🔗752. 打开转盘锁

在这里插入图片描述

2.思路

  • 分析:题目要求我们从0000开始,根据旋转规则,每次只能向上或向下旋转一次四个数位的其中一位,通过不断的旋转直到四位数字为最终答案target
  • 关键:把问题抽象成一张图,当前4位数字密码为节点,通过当前密码状态选择一个数位向上或向下旋转一次得到下一个四位数(节点),每个节点可以实现2x2x2x2 = 8种选择得到下一个节点;即:该问题抽象为一张图,每个节点有8个相邻节点,我们需要从起始节点"0000"以最短路径寻找到我们的目标节点target (注意还有限制条件)

在这里插入图片描述
有了上述分析后,大概有对这个题有了一个这个题的把握和认识,但是其实还有很多细节没有分析到。当然,一下子其实分析不到所有的细节是很正常,我们一步一步来,慢慢完善。

  1. 首先,我们先不考虑【死亡密码】的限制,想一想,从"0000"出发,穷举出所有密码的可能,我们需要怎么做;也就是说,从"0000"出发,每个节点有8个相邻,我们以BFS遍历出所有密码
//BFS框架,打印出所有可能的密码
void BFS(string target){queue<string> q;q.push("0000");while(!q.empty()){int sz = q.size();//将当前层(也就是当前队列)中节点向下一层(周围)扩散for(int i = 0; i < sz; i++){//获取当前节点string cur = q.front(); q.pop();//判断是否达到终点cout << cur << endl;//将当前节点相邻的周围节点(也就是下一层的)加入到队列for(int j = 0; j < 4; j++){//当前位向上拨string up = plusOne(cur, j);q.push(up);//当前位向下拨string down = minusOne(cur, j);q.push(down);}}//在这里增加步数}
}

上面代码中的plusOneminusOne函数的代码如下:

string plusOne(string s, int j) {if (s[j] == '9') s[j] = '0';else s[j] += 1;return s;
}string minusOne(string s, int j) {if (s[j] == '0') s[j] = '9';else s[j] -= 1;return s;
}
  1. 当然,上述代码还有很多问题
  • 首先,会走回头路,比如从"0000""1000"但是"1000"的8个相邻点里面又有"0000"这样下去会产生死循环。这个时候就需要额外加一个集合数据结构visited,记录下来已经走过的节点,在往下扩散的时候(也就是往队列里面加元素的时候)进行判断,是否已经遍历过。
  • 没有终止条件,这个其实挺好改的,只需要在遍历到那个具体的节点的时候加一个判断条件,遇到target结束循环并返回即可。
  • 没有对限制条件(不能出现死亡密码)的处理

3.代码

class Solution {
public:string plusOne(string s, int j) {if (s[j] == '9') s[j] = '0';else s[j] += 1;return s;
}string minusOne(string s, int j) {if (s[j] == '0') s[j] = '9';else s[j] -= 1;return s;
}int openLock(vector<string>& deadends, string target) {unordered_set<string> deads; //记录限制条件:需要跳过的死亡密码for(string s : deadends){deads.insert(s);}//记录已经穷举过的密码,防止走回头路unordered_set<string> visited;queue<string> q;int step = 0;//从起点开始进行BFSq.push("0000");visited.insert("0000");while(!q.empty()){int sz = q.size();for(int i = 0; i < sz; i++){string cur = q.front();q.pop();if(deads.count(cur)) continue; //分支限界,在遍历到这个节点的时候限制就行//判断是否达到终止条件if(cur == target) return step;for(int j = 0; j < 4; j++){string left = plusOne(cur,j);if(!visited.count(left) ){q.push(left);visited.insert(left);}string right = minusOne(cur,j);if(!visited.count(right)) {q.push(right);visited.insert(right);}}}step++;}return -1;}
};

2.3:布线问题

1.题目

印刷电路板将布线区域划分成n*m个方格阵列,精确的电路布线问题要求确定连接方格a的中点到方格b的中点的最短布线问题。在布线时,电路只能沿着直线或直角布线。为了避免线路相交,已布了线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
在这里插入图片描述

2.思路

  • 分析:其实这个题就是有障碍物的迷宫问题。本质上也是抽象为图问题,格子上每一个点的坐标位置就是一个节点,一个节点可以往上下左右4个方向行走到达其他位置(节点);注意限制条件:不能在障碍物(也就是题目所说的封锁方格)处进行遍历,所以遇到该限制条件就跳过且不扩展该样的节点。
  • 关键:
  1. 用一个队列来进行BFS搜索,找到起点位置到终点位置的最短路径
  2. 用一个二维布尔数组visited来存储对应位置处方格是否被访问过(避免走回头路)
  3. 用一个二维数组来记录封锁方格(其实就是存储初始棋盘grid[][]
  4. 如果需要记录最终得到的那个最短路径,可以设置一个以pair<int, int>为元素的二维数组,记录(x,y)位置是由parent[x][y]得来的(记录路径,能从目标节点反推回去完整路径)

3.代码

#include <iostream>
#include <vector>
#include <queue>
#include <tuple>using namespace std;//定义节点可以扩展的四个方向:上下左右{x,y}
vector< pair<int, int> > dirs ={{-1,0}, {1,0}, {0,-1}, {0, 1}};/*辅助函数,判断该节点是否合法1. 不能超过整个迷宫(电线板)边界2. 不能被访问过(不能走回头路)3. 不能是封锁节点(即迷宫里的障碍物
*/
bool isValid(int x, int y, int n, int m, vector<vector<int>>& grid, vector <vector<bool>>& visited){return x >= 0 && x <n && y>=0 && y < m && grid[x][y] != -1 && !visited[x][y];
}//BFS算法求解最短路径并标记路径节点
vector <vector<int>> shortestPath(vector<vector<int>>& grid, pair<int, int> Start, pair<int, int> End){int n = grid.size();int m = grid[0].size();//队列,存储当前坐标和部署queue<tuple<int, int, int>> q;//已经访问节点的存储vector<vector<bool>> visited(n, vector<bool>(m, false));//记录当前节点是由哪个节点得来(记录路径)vector<vector<pair<int, int>>> parent(n, vector<pair<int, int>>(m, make_pair(-1, -1)));//将起点加入队列q.push(make_tuple(Start.first, Start.second, 0));visited[Start.first][Start.second] = true;bool found = false; //标记是否找到,一旦找到,路径最短, 退出while(!q.empty()){int sz = q.size();//遍历该层for(int i = 0; i < sz; i++){//先拿出当前节点auto [cur_x, cur_y, steps] = q.front();q.pop();//可选:判断当前节点是否合理(其实没必要,起点肯定一般都是合理,保证在下面扩展的时候不添加不合理的即可//判断是否达到终点if( cur_x == End.first && cur_y == End.second){found = true;break;}//扩展四个方向for(auto [dx, dy] : dirs){int newX = cur_x + dx;int newY = cur_y + dy;//判断合法if(isValid(newX, newY, n, m, grid, visited)){q.push(make_tuple(newX, newY, steps+1));visited[newX][newY] = true;//记录父节点parent[newX][newY] = make_pair(cur_x, cur_y);}}}}if(found){int x = End.first;int y = End.second;while(!(x == Start.first && y == Start.second)){grid[x][y] = 1;tie(x,y) = parent[x][y];}grid[Start.first][Start.second] = 1; //标记起点}return grid;
}int main(){//输入int n, m;cout << "请输入电路板的行数和列数:";cin >> n >> m;vector <vector<int>> grid (n, vector<int>(m, 0));cout << "请输入电路版的状态(0为空,-1为封锁状态):"<<endl;for(int i = 0; i < n; i++){for(int j = 0; j < m; j++){cin >> grid[i][j];}}int startX, startY, endX, endY;cout << "请输入起点坐标(x y):";cin >> startX >> startY;cout << "请输入终点坐标(x y):";cin >> endX >> endY;//BFS遍历vector< vector<int> > res = shortestPath(grid, {startX, startY}, {endX, endY});//输出结果cout << "结果矩阵为:"<< endl;for(const auto& row : res){for(int cell : row){cout << cell << " ";}cout << endl;}return 0;
}

在这里插入图片描述

end:本文参考

🔗labuladongBFS算法解题套路框架

🔗布线问题分支界限法求解

相关文章:

【BFS算法】广度搜索·由起点开始逐层向周围扩散求得最短路径(算法框架+题目)

0、前言 深度优先搜索是DFS&#xff08;Depth Frst Search)&#xff0c;其实就是前面所讲过的回溯算法&#xff0c;它的特点和它的名字一样&#xff0c;首先在一条路径上不断往下&#xff08;深度&#xff09;遍历&#xff0c;获得答案之后再返回&#xff0c;再继续往下遍历。…...

微信小程序---登录

手机号登录 手机号快速验证和手机号实时验证区别 手机号快速验证组件&#xff0c;平台会对号码进行验证&#xff0c;但不保证是实时验证&#xff1b;收费0.0.3元手机号实时验证组件&#xff0c;在每次请求时&#xff0c;平台均会对用户选择的手机号进行实时验证。收费0.0.4元…...

IPython大师课:提升数据科学工作效率的终极工具

IPython是一个增强的Python交互式shell&#xff0c;它提供了丰富的功能和易用性改进&#xff0c;特别适合进行数据分析、科学计算和一般的Python开发。本文将全面介绍IPython的基本概念、使用方法、主要作用以及注意事项。 一、IPython简介 1. IPython的起源 IPython最初由Fe…...

抖音素材网站平台有哪些?素材下载网站库分享

在这个视觉信息充斥的时代&#xff0c;抖音已经成为众多自媒体人展示才华的舞台。要在众多创作者中脱颖而出&#xff0c;不仅需要独特的创意&#xff0c;还需要优质的素材来支持你的内容制作。今天&#xff0c;我将介绍几个为抖音视频提供高品质素材的网站&#xff0c;包括国内…...

MODBUS TCP协议实例数据帧详细分析

MODBUS TCP协议实例数据帧详细分析 1.简介 2.ModbusTCP数据帧 2.1.报文头MBAP 2.2.帧结构PDU 3.ADU详细结构 3.1. 0x01&#xff1a;读线圈 3.2. 0x02&#xff1a;读离散量输入 3.3. 0x03&#xff1a;读保持寄存器 3.4. 0x04&#xff1a;读输入寄存器 3.5. 0x05&#xff1a;写单…...

Spring Boot启动与运行机制详解:初学者友好版

Spring Boot启动与运行机制详解&#xff1a;初学者友好版 随着微服务的兴起和容器化部署的流行&#xff0c;Spring Boot以其快速搭建、简单配置和自动化部署的特性&#xff0c;成为了众多开发者的首选。对于初学者而言&#xff0c;理解Spring Boot的启动与运行机制是掌握其精髓…...

Ubuntu 22.04 解决 firefox 中文界面乱码

问题复现 在为Ubuntu 22.04 Server安装完整的GNOME 42.01桌面后&#xff0c;将桌面语言设置为中文时&#xff0c;打开Firefox可能会出现中文乱码的问题。经过网上调查发现&#xff0c;这个问题是由Snap软件包引起的。 解决方案 为了避免在Ubuntu 22.04中文模式下的乱码问题…...

前端面试题日常练-day77 【面试题】

题目 希望这些选择题能够帮助您进行前端面试的准备&#xff0c;答案在文末 在Sass中&#xff0c;以下哪个功能用于将样式规则应用于多个选择器&#xff1f; a) extend b) mixin c) import d) include Sass中的嵌套规则&#xff08;Nested Rules&#xff09;有助于实现以下哪个…...

团队协同渗透测试报告输入输出平台部署

目录 简介 文章来源 部署环境 文件下载 开始安装 系统初始化 免责声明 结语 简介 因应监管部需求&#xff0c;国内访问Docker源pull镜像开始变得复杂且困难起来了&#xff0c;大佬github给的在线/离线安装脚本跑了很久也无法拉取到镜像&#xff0c;所以将以前的镜像打…...

vue3-父子通信

一个简单的vue3子组件调用父组件方法的demo <template> <div> <h2>Parent Component父组件</h2> <ChildComponent notify-parent"handleParentMethod" /> </div> </template> <script> import { ref } fr…...

微信小程序—页面滑动,获取可视区域数据

需求&#xff1a;页面有一列表&#xff0c;获取可视区域的数据&#xff1b;滑动过程中不处理&#xff0c;停止滑动后才获取。 实现原理&#xff1a;获取列表中每个条目的位置信息&#xff08;元素顶部距可视区域顶部的距离&#xff09;&#xff0c;和可视区域比较&#xff0c;…...

C#语言进阶(一)—委托

总目录 C# 语法总目录 委托 委托1. 基本用法2.委托作为方法参数3.多播委托4.实例对象方法、静态方法与委托之间的关系5. 委托类型参数为泛型6. System空间下的 Func 委托和 Action 委托 委托 委托类似于CPP中的函数指针。它定义了一个方法类型&#xff0c;这个方法类型有返回类…...

VST3音频插件技术介绍

一.概述 1.VST3介绍 VST3&#xff08;Virtual Studio Technology 3&#xff09;是一种音频插件格式&#xff0c;由Steinberg公司开发&#xff0c;用于在数字音频工作站&#xff08;DAW&#xff09;中使用。VST3插件可以是模拟合成器、鼓机、混响器、压缩器等多种类型的音频处理…...

MySQL数据库管理 二

1、数据表高级操作 &#xff08;1&#xff09;克隆表 方法一&#xff1a; create table 新表名 like 旧表名; #克隆表结构 insert into 新表名 select * from 旧表名; #克隆表数据 #此方法能保证 新表的表结构、表数据 跟旧表都是一致的 方法二&#x…...

android system UI 基础的基础

Android 系统中的 SystemUI 是一种特殊的应用程序&#xff0c;它负责管理和显示设备的用户界面组件&#xff0c;例如状态栏、导航栏和最近任务列表等。SystemUI 是在 Android 启动过程中由 Zygote 进程启动的。以下是 SystemUI 启动过程的详细步骤&#xff1a; SystemUI 启动过…...

ARM32开发——GD32F4定时器查询

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录...

【机器学习】第7章 集成学习(小重点,混之前章节出题但小题)

一、概念 1.集成学习&#xff0c;顾名思义&#xff0c;不是一个玩意&#xff0c;而是一堆玩意混合到一块。 &#xff08;1&#xff09;基本思想是先 生成一定数量基学习器&#xff0c;再采用集成策略 将这堆基学习器的预测结果组合起来&#xff0c;从而形成最终结论。 &#x…...

代码随想录——子集Ⅱ(Leecode 90)

题目链接 回溯 class Solution {List<List<Integer>> res new ArrayList<List<Integer>>();List<Integer> list new ArrayList<Integer>();boolean[] used; public List<List<Integer>> subsetsWithDup(int[] nums) {use…...

vue关闭页面时触发的函数(ai生成)

在Vue中&#xff0c;可以通过监听浏览器的beforeunload事件来在关闭页面前触发函数。这里是一个简单的示例&#xff1a; new Vue({el: #app,methods: {handleBeforeUnload(event) {// 设置returnValue属性以显示确认对话框event.returnValue 你确定要离开吗&#xff1f;;// 在…...

马尔可夫性质与Q学习在强化学习中的结合

马尔可夫性质是强化学习&#xff08;RL&#xff09;算法的基础&#xff0c;特别是在Q学习中。马尔可夫性质指出&#xff0c;系统的未来状态只依赖于当前状态&#xff0c;而与之前的状态序列无关。这一性质简化了学习最优策略的问题&#xff0c;因为它减少了状态转移的复杂性。 …...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...