【算法】BFS解决最短路径问题

【算法】BFS解决最短路径问题
📢博客主页:https://blog.ZEEKLOG.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 ZEEKLOG🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

文章目录


📢前言


🏳️‍🌈一、概念

**BFS(广度优先搜索)**在图论算法中有着广泛的应用,尤其是在解决最短路径问题上表现出色。本文将详细介绍如何使用 C++ 实现 BFS 来解决最短路径问题。

广度优先搜索是一种用于图遍历的算法,它从起始节点开始,逐步探索其相邻节点,然后再探索相邻节点的相邻节点,以此类推。这种算法在解决最短路径问题时非常有用,因为它能够保证找到的路径是最短的。

在 C++ 中,可以使用队列来实现 BFS。队列的特点是先进先出,这与 BFS 的遍历方式相符合。首先,将起始节点加入队列,然后从队列中取出一个节点,探索其相邻节点,并将未访问过的相邻节点加入队列。重复这个过程,直到找到目标节点或者队列为空。

BFS 在解决各种实际问题中都有广泛的应用,比如迷宫问题、网络路由问题等。在迷宫问题中,BFS 可以用来找到从起点到终点的最短路径;在网络路由问题中,BFS 可以用来找到两个节点之间的最短路由。
接下来,我们将通过具体的代码示例来展示如何使用 C++ 实现 BFS 来解决最短路径问题。

在这里插入图片描述

🏳️‍🌈二、问题描述

以迷宫问题为例,在一张由 0 和 1 构成的图中,1 表示障碍,0 表示通路。给定起点 S 和终点 T,求从 S 到 T 的最短路长度并输出路径。

在解决这个问题时,我们可以使用广度优先搜索(BFS)算法。BFS 是一种用于图遍历的算法,它从起始节点开始,逐步探索其相邻节点,然后再探索相邻节点的相邻节点,以此类推。在迷宫问题中,我们可以将每个格子看作一个节点,相邻的格子之间有边相连。

首先,我们需要定义一个数据结构来表示迷宫。可以使用二维数组来存储迷宫的状态,其中 0 表示通路,1 表示障碍。同时,我们还需要定义一个队列来存储待探索的节点。队列的特点是先进先出,这与 BFS 的遍历方式相符合。

接下来,我们将起始节点加入队列,并标记为已访问。然后,从队列中取出一个节点,探索其相邻节点。如果相邻节点是通路且未被访问过,则将其加入队列,并标记为已访问。重复这个过程,直到找到目标节点或者队列为空。

在探索过程中,我们可以使用一个数组来记录每个节点到起始节点的距离。初始时,起始节点的距离为 0,其他节点的距离为无穷大。当探索到一个节点时,将其距离更新为上一个节点的距离加 1。这样,当找到目标节点时,我们就可以得到从起始节点到目标节点的最短路长度。

为了输出路径,我们可以在探索过程中记录每个节点的前驱节点。当找到目标节点时,从目标节点开始,沿着前驱节点回溯,直到到达起始节点,这样就可以得到从起始节点到目标节点的路径。

#include<iostream>#include<queue>constint maxn =1010;char mp[maxn][maxn];int dist[maxn][maxn];int n;int sx, sy, tx, ty;int dx[4]={1,0,0,-1}, dy[4]={0,-1,1,0};char dir[4]={'D','L','R','U'};voidbfs(){for(int i =0; i < n; i ++)for(int j =0; j < n; j ++) dist[i][j]=1e9; dist[tx][ty]=0; std::queue<std::pair<int,int>> q; std::pair<int,int> t; t.first = tx; t.second = ty; q.push(t);while(q.size()){ t = q.front(); q.pop();for(int i =0; i <4; i ++){int nx = t.first + dx[i];int ny = t.second + dy[i];if(nx >=0&& nx < n && ny >=0&& ny < n && dist[nx][ny]==1e9&& mp[nx][ny]!='1'){ dist[nx][ny]= dist[t.first][t.second]+1; q.push(std::make_pair(nx, ny));}}}}intmain(){ std::cin >> n;for(int i =0; i < n; i ++){for(int j =0; j < n; j ++){ std::cin >> mp[i][j];if(mp[i][j]=='S'){ sx = i; sy = j;}if(mp[i][j]=='T'){ tx = i; ty = j;}}}bfs(); std::cout << dist[sx][sy]<< std::endl;int x = sx, y = sy;while(x!= tx || y!= ty){for(int i =0; i <4; i ++){int nx = x + dx[i];int ny = y + dy[i];if(nx >=0&& nx < n && ny >=0&& ny < n && mp[nx][ny]!='1'){if(dist[x][y]== dist[nx][ny]+1){ std::cout << dir[i]<< std::endl; x = nx; y = ny;break;}}}}return0;}

这段代码首先实现了 BFS 算法来计算从起点到终点的最短路长度,然后通过回溯找到最短路径并输出。在实际应用中,可以根据具体的问题需求对代码进行适当的调整和扩展。

🏳️‍🌈三、求解思路

  1. **BFS 特性:**BFS 是由队列实现,具有先进先出的特性。在解决最短路径问题时,队列的这种特性保证了先访问距离起点较近的节点,再逐渐扩展到距离较远的节点。就像在迷宫问题中,我们从起点开始,先探索起点周围的节点,然后再依次探索这些节点周围的节点,确保每次探索的都是距离起点最近的未访问节点。
  2. **求最短路长度:**从终点 T 开始倒着搜索,用一个数组 dist [][] 表示每个点到终点 T 的最短路径,如果一个点能由上一个点一步走到,则将该点入队,直到队列为空即遍历完所有的点。具体来说,我们首先将终点的距离初始化为 0,然后将终点入队。从队列中取出一个节点,遍历它的相邻节点,如果相邻节点未被访问过且不是障碍,就将其距离更新为当前节点的距离加 1,并将其入队。这样不断重复,直到队列为空,此时 dist 数组中就存储了每个点到终点的最短路径长度。
  3. **求路径:**在 dist 数组的基础上,从起点开始搜索,如果有一点能由当前点一步走到,则该点一定在最短路径上。我们从起点开始,遍历它的相邻节点。对于每个相邻节点,如果它能由当前节点一步走到(即相邻节点的距离等于当前节点的距离加 1),那么该相邻节点一定在最短路径上。我们将这个相邻节点加入路径中,并继续从这个节点出发进行搜索,直到到达终点。这样就可以得到从起点到终点的最短路径。

🏳️‍🌈四、代码实现

  1. 初始化:将 dist 数组初始化为无穷大,终点到终点距离设为零,用队列存储待搜索的点。
    首先,我们需要定义一个二维数组 dist 来表示每个点到终点的距离。在初始化阶段,将 dist 数组中的所有元素初始化为无穷大,表示这些点到终点的距离未知。同时,将终点到终点的距离设为零,即 dist[tx][ty]=0。
    然后,我们使用一个队列来存储待搜索的点。队列的特点是先进先出,这与广度优先搜索的遍历方式相符合。在 C++ 中,可以使用 std::queue 来实现队列。我们定义一个队列 q,并将终点作为初始元素加入队列中。具体代码如下:
for(int i =0; i < n; i ++)for(int j =0; j < n; j ++) dist[i][j]=1e9; dist[tx][ty]=0; std::queue<std::pair<int,int>> q; std::pair<int,int> t; t.first = tx; t.second = ty; q.push(t);
  1. BFS 搜索:每次取出队头元素,遍历其四个方向,如果满足条件(未越界、未走过、可走),则更新该点到终点的距离并将其入队。
    在 BFS 搜索阶段,我们从队列中取出队头元素,然后遍历该元素的四个方向。如果新的点满足条件(未越界、未走过、可走),则更新该点到终点的距离,并将其加入队列中。
    具体来说,我们使用一个循环来遍历四个方向。对于每个方向,我们计算新的点的坐标,并检查该点是否在迷宫范围内、是否未被访问过以及是否是通路。如果满足这些条件,我们将该点的距离更新为当前点的距离加一,并将其加入队列中。代码如下:
while(q.size()){ t = q.front(); q.pop();for(int i =0; i <4; i ++){int nx = t.first + dx[i];int ny = t.second + dy[i];if(nx >=0&& nx < n && ny >=0&& ny < n && dist[nx][ny]==1e9&& mp[nx][ny]!='1'){ dist[nx][ny]= dist[t.first][t.second]+1; q.push(std::make_pair(nx, ny));}}}
  1. 输出路径:从起点开始,根据 dist 数组判断下一个点是否在最短路径上,输出方向并更新坐标。
    在输出路径阶段,我们从起点开始,根据 dist 数组判断下一个点是否在最短路径上。如果下一个点的距离等于当前点的距离加一,那么该点一定在最短路径上。
    我们从起点开始,遍历它的相邻节点。对于每个相邻节点,如果它能由当前节点一步走到(即相邻节点的距离等于当前节点的距离加一),那么该相邻节点一定在最短路径上。我们将这个相邻节点加入路径中,并继续从这个节点出发进行搜索,直到到达终点。
    在搜索过程中,我们可以使用一个数组来记录每个点的方向。例如,如果当前点是由上一个点向右移动得到的,那么我们可以将当前点的方向记录为 R。这样,在输出路径时,我们可以根据每个点的方向来输出路径。
    具体代码如下:
int x = sx, y = sy;while(x!= tx || y!= ty){for(int i =0; i <4; i ++){int nx = x + dx[i];int ny = y + dy[i];if(nx >=0&& nx < n && ny >=0&& ny < n && mp[nx][ny]!='1'){if(dist[x][y]== dist[nx][ny]+1){ std::cout << dir[i]<< std::endl; x = nx; y = ny;break;}}}}

🏳️‍🌈例题分析

❤️1926. 迷宫中离入口最近的出口

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

在这里插入图片描述


解题思路分析
这是一道典型的迷宫寻路问题,可以使用广度优先搜索(BFS)算法来解决。

首先,定义了迷宫的行数m、列数n,以及一个二维数组vis用于记录每个格子是否被访问过,初始化为false。还定义了四个方向数组kx和ky,分别表示上下左右四个方向的偏移量。

nearestExit函数中,先获取迷宫的行数和列数,将入口坐标加入队列q,并标记入口已访问。然后开始进行 BFS 搜索。

每次循环,先将步数step加 1,表示进入新的一层搜索。接着遍历当前队列中的所有节点(当前层的所有可到达格子),对于每个节点,尝试向四个方向移动。

如果移动后的坐标x和y在迷宫范围内,且对应格子是空格子(maze[x][y] == '.')且未被访问过,就判断是否到达了迷宫边界(x == 0 || x == m - 1 || y == 0 || y == n - 1),如果到达了边界,说明找到了离入口最近的出口,直接返回当
前步数step。如果没有到达边界,就将该格子坐标加入队列q,并标记为已访问。

如果队列为空,说明没有找到出口,返回 -1。

classSolution{int m, n;// 用于记录每个格子是否被访问过,初始化为falsebool vis[110][110]={false};// 表示上下左右四个方向的x偏移量int kx[4]={0,0,-1,1};// 表示上下左右四个方向的y偏移量int ky[4]={1,-1,0,0};public:intnearestExit(vector<vector<char>>& maze, vector<int>& entrance){// 获取迷宫的行数 m = maze.size();// 获取迷宫的列数 n = maze[0].size();// 定义一个队列,用于存储待探索的格子坐标 queue<pair<int,int>> q;// 将入口坐标加入队列 q.push({entrance[0], entrance[1]});// 标记入口已被访问 vis[entrance[0]][entrance[1]]=true;// 记录步数int step =0;// 当队列不为空时,进行广度优先搜索while(q.size()){// 步数加1,表示进入新的一层搜索 step++;// 获取当前层的节点数量int sz = q.size();for(int i =0; i < sz;++i){// 取出队列头部的坐标auto[a, b]= q.front(); q.pop();// 尝试向四个方向移动for(int k =0; k <4;++k){// 计算移动后的坐标int x = a + kx[k], y = b + ky[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 step;// 将新坐标加入队列 q.push({x, y});// 标记新坐标已被访问 vis[x][y]=true;}}}}// 如果没有找到出口,返回 -1return-1;}};

🧡433. 最小基因变化

433. 最小基因变化

在这里插入图片描述

这是一道求最短变换路径的问题,同样可以使用广度优先搜索(BFS)算法来解决。

首先定义了一个字符数组k,包含了基因序列中可能出现的四种字符('A'、'C'、'G'、'T')。
minMutation函数中,创建了两个unordered_set容器,vis用于记录已经访问过的基因序列,hash用于存储基因库中的基因序列,通过遍历bank将基因库中的基因序列加入hash。

然后进行一系列的初始化判断,如果起始基因序列startGene和目标基因序列endGene相同,直接返回 0;如果目标基因序列不在基因库中,返回 -1。

接着将起始基因序列startGene加入队列q,并标记为已访问。开始进行 BFS 搜索,每次循环步数step加 1,表示进入新的一层搜索。

在每一层搜索中,取出队列头部的基因序列t,对于该序列的每个字符位置i(因为基因序列长度为 8),尝试将其替换为四种可能的字符k[j](通过内层循环),得到新的基因序列tmp

如果新序列tmp等于目标基因序列endGene,说明找到了最短变换路径,返回当前步数step。如果tmp在基因库hash中且未被访问过,就将其加入队列q,并标记为已访问。

如果队列为空,说明没有找到从startGeneendGene的变换路径,返回 -1。

classSolution{// 定义一个字符数组,包含基因序列中可能出现的四种字符char k[4]={'A','C','G','T'};public:intminMutation(string startGene, string endGene, vector<string>& bank){// 用于记录已经访问过的基因序列 unordered_set<string> vis;// 用于存储基因库中的基因序列 unordered_set<string> hash;// 将基因库中的基因序列加入hash集合for(auto e : bank) hash.insert(e);// 如果起始基因序列和目标基因序列相同,直接返回0if(startGene == endGene)return0;// 如果目标基因序列不在基因库中,返回 -1if(!hash.count(endGene))return-1;// 定义一个队列,用于存储待探索的基因序列 queue<string> q;// 将起始基因序列加入队列 q.push(startGene);// 标记起始基因序列已被访问 vis.insert(startGene);// 记录步数int step =0;// 当队列不为空时,进行广度优先搜索while(q.size()){// 步数加1,表示进入新的一层搜索++step;// 获取当前层的基因序列数量int sz = q.size();while(sz--){// 取出队列头部的基因序列 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]= k[j];// 如果新序列等于目标基因序列,返回当前步数if(tmp == endGene)return step;// 如果新序列在基因库中且未被访问过,将其加入队列并标记为已访问if(hash.count(tmp)&&!vis.count(tmp)) q.push(tmp), vis.insert(tmp);}}}}// 如果没有找到从起始基因序列到目标基因序列的变换路径,返回 -1return-1;}};

👥总结

本篇博文对 【算法】BFS解决最短路径问题 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

Read more

Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断技术债堆砌 前言 在 OpenHarmony 的大规模团队协作中,代码质量是团队的生命线。如果没有有效的约束,不符合规范的代码(甚至是无法通过静态分析的代码)会轻易地通过 git commit 进入代码库,导致 CI 构建频繁失败。git_hooks 库为 Flutter 开发者提供了一种轻量级的脚本化方案,可以在 Git 的关键生命周期(如提交前、推送前)自动运行检查。本文将带大家在鸿蒙端实战适配该库,夯实自动化工程的地基。 一、原直线性 / 概念介绍 1.1 基础原理/概念介绍 git_hooks 的核心逻辑是基于 Git

By Ne0inhk

完全免费!用阿里开源 CoPaw 养一只属于自己的 AI 小助理(魔搭启动,亲测有效)

先说一个小插曲:前几天我写了一篇介绍 Maxclaw 的文章,当时还是免费的,结果文章发出去没多久,Minimax 就悄悄改了规则,变成 39 元一个月起步了。当然,39 元其实也不贵——毕竟你去闲鱼搜"openclaw 代安装",随便一个人工服务都要 50 块往上走。但既然有完全免费的方案,为什么不用呢? 今天这篇,就给大家介绍一个我亲自跑通的、完全免费的方案:用阿里开源的 CoPaw,在魔搭创空间里一键启动,服务器免费,Token 每天 2000 次免费调用,不用装任何本地环境,浏览器打开就能用。 CoPaw 是什么?先用一分钟搞清楚 很多人第一次听到 CoPaw 这个名字,会以为是某种宠物应用。其实它的全称是 Co Personal Agent Workstation,是阿里

By Ne0inhk
Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构

Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全球化开源协作、涉及极大规模的跨端 CI/CD 流水线构建、多机型自动化兼容性测试及严苛的代码准入控制背景下,如何实现一套既能深度对接 GitHub Actions 核心底脚(Toolkits)、又能提供原生 Dart 编程感且具备工业级日志输出与状态管理的“自动化控制基座”,已成为决定应用研发迭代频率与交付质量稳定性的关键。在鸿蒙项目这类强调多模块(HAP/HSP)并行构建与分布式证书签名校验的环境下,如果 CI 脚本依然依赖大量零散的 Shell 拼接,由于由于环境变量的微差异,极易由于由于“脚本不可维护”导致鸿蒙应用在自动化发布环节频繁由于由于故障导致阻塞。

By Ne0inhk

LFM2.5-1.2B-Thinking惊艳效果展示:Ollama本地运行下长文本推理

LFM2.5-1.2B-Thinking惊艳效果展示:Ollama本地运行下长文本推理 在本地设备上运行强大的AI模型,曾经是科幻电影中的场景。如今,随着LFM2.5-1.2B-Thinking模型的发布,这一切变成了现实。这个仅有12亿参数的"小模型"却拥有令人惊叹的长文本推理能力,真正实现了"高质量AI装入口袋"的愿景。 1. 模型核心能力概览 LFM2.5-1.2B-Thinking是专为设备端部署设计的新型混合模型,它在LFM2架构基础上进行了深度优化。这个模型最大的亮点在于:用极小的体积实现了接近大模型的性能表现。 1.1 技术特点解析 LFM2.5系列通过扩展预训练和强化学习进行了全面优化。预训练数据量从10T token扩展至28T token,采用了大规模多阶段强化学习训练方式。这意味着模型在保持小巧体积的同时,获得了更丰富的知识储备和更强的推理能力。 核心优势对比: 特性传统大模型LFM2.5-1.2B-Thinking参数量70亿+12亿内存占用4GB+<1GB推理速度较慢极快(AMD CPU上239 tok/

By Ne0inhk