迷宫中的最短路径:如何用 BFS 找到最近出口【算法模板】

news2024/10/2 23:54:37

如何通过广度优先搜索(BFS)求解迷宫问题

在这篇文章中,我们将学习如何使用 广度优先搜索(BFS) 解决一个典型的迷宫问题,具体是从迷宫的一个入口出发,找到最近的出口。我们将一步步分析 BFS 是如何工作的,并展示详细的 C++ 实现代码,帮助你更好地理解算法的原理和细节。

1. 问题描述

假设我们有一个二维迷宫,其中 + 代表墙壁,. 代表空地。迷宫的某些空地可能位于迷宫边缘,作为出口。我们需要找到从迷宫内给定的 入口 到达最近 出口 的最短路径。

  • 输入:迷宫矩阵 maze,以及入口坐标 entrance
  • 输出:从入口到最近出口的步数。如果不存在出口,则返回 -1

1926. 迷宫中离入口最近的出口 - 力扣(LeetCode)

2. 广度优先搜索(BFS)介绍

广度优先搜索 是一种用于搜索树或图的遍历算法,适合用来解决 最短路径问题。BFS 从起始点开始,每次遍历离起始点最近的节点,逐层向外扩展,因此 BFS 保证了找到的第一个解是最短路径。

迷宫问题中的 BFS 思路:

  • 从入口开始进行 BFS,逐步检查四个方向的邻居(上、下、左、右),记录步数。
  • 如果遇到边界上的出口,则返回步数,表示找到最短路径。
  • 如果遍历完整个迷宫没有找到出口,返回 -1

3. BFS 算法的实现步骤

以下是解题的主要步骤:

  1. 初始化数据结构

    • 使用队列(queue)来存储当前要探索的点。
    • 使用 visited 数组标记哪些点已经访问过,防止重复访问。
  2. 方向数组(directions-vector)

    • 使用一个二维向量 directions,存储四个方向的坐标偏移量,分别代表 上、下、左、右。每次我们检查当前点的四个邻居,看看能否向该方向移动。
  3. BFS 主循环

    • 在 BFS 主循环中,每次从队列中取出当前层的节点,逐步遍历它们的四个邻居。
    • 如果某个邻居位于迷宫边界且是空地,我们就找到了出口,返回当前步数。
  4. 层次遍历

    • 每当完成一层的遍历,步数 steps 增加。
  5. 特殊情况处理

    • 需要确保入口不算作出口,避免错误判断。

4. C++ 代码实现

我们通过 C++ 实现这个 BFS 算法,详细注释解释了每个步骤:

#include <vector>
#include <queue>
using namespace std;

class Solution {
public:
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        int m = maze.size();    // 行数
        int n = maze[0].size(); // 列数
        
        // 四个方向:上、下、左、右
        vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        // 初始化队列和 visited 数组
        queue<pair<int, int>> q;
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        
        // 将入口加入队列并标记为访问过
        int startX = entrance[0], startY = entrance[1];
        q.push({startX, startY});
        visited[startX][startY] = true;
        
        // 记录当前的步数
        int steps = 0;
        
        // 开始 BFS
        while (!q.empty()) {
            int size = q.size();  // 当前层的节点数量
            
            // 遍历当前层的每一个节点
            for (int i = 0; i < size; ++i) {
                auto [x, y] = q.front();
                q.pop();
                
                // 检查当前点是否是出口(边界上的空格)
                if ((x == 0 || x == m - 1 || y == 0 || y == n - 1) && !(x == startX && y == startY)) {
                    return steps;
                }
                
                // 向四个方向扩展
                for (auto [dx, dy] : directions) {
                    int newX = x + dx;
                    int newY = y + dy;
                    
                    // 检查是否可以移动到新的点
                    if (newX >= 0 && newX < m && newY >= 0 && newY < n && maze[newX][newY] == '.' && !visited[newX][newY]) {
                        q.push({newX, newY});
                        visited[newX][newY] = true;  // 标记为已访问
                    }
                }
            }
            steps++;  // 当前层遍历完后步数增加
        }
        
        return -1;  // 如果遍历完迷宫没有找到出口
    }
};

5. 代码细节讲解

  1. 方向数组(directions

    • directions 数组包含了四个方向的坐标偏移量:上({-1, 0})、下({1, 0})、左({0, -1})、右({0, 1})。这种方法能避免手写多个 if 判断条件,使代码更加简洁。
    • 在 BFS 中,遍历当前点的四个邻居时,我们通过这个方向数组,快速获取新的坐标。
  2. 队列和层次遍历

    • queue<pair<int, int>> q; 是我们使用的队列,它存储了当前层要探索的坐标。
    • 在 BFS 主循环中,每次取出当前层的所有节点,遍历完这一层后步数 steps++
  3. 出口判断

    • 当一个点位于迷宫边界且不是入口时,它就被认为是出口。
  4. 时间复杂度和空间复杂度

    • 时间复杂度:由于每个点最多被访问一次,BFS 的时间复杂度为 O(m * n),其中 m 是迷宫的行数,n 是列数。
    • 空间复杂度:BFS 使用了一个 queue 和一个 visited 数组,因此空间复杂度同样是 O(m * n)

6. 示例分析

我们以几个具体的示例来展示代码的执行过程。

示例 1:
maze = [["+","+",".","+"],
        [".",".",".","+"],
        ["+","+","+","."]]
entrance = [1,2]

在这里插入图片描述

  • 入口坐标是 (1,2)
  • 第一次从 (1,2) 开始,向左移动到 (1,1),向上移动到 (0,2),而 (0,2) 是一个边界点,符合出口条件。
  • 返回步数 1
示例 2:
maze = [["+","+","+"],
        [".",".","."],
        ["+","+","+"]]
entrance = [1,0]

在这里插入图片描述

  • 入口坐标是 (1,0)
  • 第一次从 (1,0) 开始,向右移动到 (1,1)
  • 第二次向右移动到 (1,2),并且 (1,2) 是边界点,返回步数 2
示例 3:
maze = [[".","+"]]
entrance = [0,0]

在这里插入图片描述

  • 迷宫只有一个空格,且入口本身就是边界,没有其他出口。
  • 因此,返回 -1

7. BFS 模板总结

BFS 是解决 最短路径问题 的常用算法。常见的 BFS 模板如下:

// 初始化队列和访问数组
queue<pair<int, int>> q;
vector<vector<bool>> visited(m, vector<bool>(n, false));

// 将起点加入队列并标记为访问过
q.push({startX, startY});
visited[startX][startY] = true;

// BFS 主循环
while (!q.empty()) {
    int size = q.size(); // 获取当前层的节点数量
    
    // 遍历当前层的每个节点
    for (int i = 0; i < size; ++i) {
        auto [x, y] = q.front();
        q.pop();
        
        // 执行某些操作
        
        // 向四个方向扩展
        for (auto [dx, dy] : directions) {
            int newX = x + dx;
            int newY = y + dy;
            
            // 检查是否可以移动到新的点并继续 BFS
           

 if (/* 满足条件 */) {
                q.push({newX, newY});
                visited[newX][newY] = true;
            }
        }
    }
}

8. 总结

通过这篇文章,我们详细讨论了如何通过 广度优先搜索(BFS) 解决迷宫问题。我们展示了完整的 C++ 实现代码,逐步分析了 BFS 的原理,并总结了常见的 BFS 模板。

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

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

相关文章

初识CyberBattleSim

现在许多企业都在使用AD域服务进行管理&#xff0c;我们现在通俗理解里面蕴含着许多重要资产。 对于这个东西有下列的描述: 1、攻击他能够获得用户权限 2、里面存在许多的计算机资产&#xff0c;当攻击者攻击其中的一台机器&#xff0c;可以通过某种手段在域中的环境横向移动…

golang rpc

RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的理解是一个节点请求另一个节点提供的服务&#xff0c;对应rpc的是本地过程调用&#xff0c;函数调用是最常用的本地过程调用&#xff0c;将本地过程调用变成远程调用会面临着各种问题。 以两数…

第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC

21.1 事前准备 CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number ) ) ENGINE INNODB CHARSET utf8;INSERT INTO hero VALUES ( 1, 刘备, 蜀 );21.2 事务隔离级别 在保证事务隔离性的前提下&#xff0c;使用不同的隔…

【Burp入门第三十三篇】IP Rotate 插件实现IP轮换爆破

Burp Suite是一款功能强大的渗透测试工具,被广泛应用于Web应用程序的安全测试和漏洞挖掘中。 本专栏将结合实操及具体案例,带领读者入门、掌握这款漏洞挖掘利器 读者可订阅专栏:【Burp由入门到精通 |CSDN秋说】 文章目录 正文安装步骤使用步骤应用场景实战文章正文 在 Burp…

基于SpringBoot+Vue+MySQL的智能垃圾分类系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着城市化进程的加速&#xff0c;垃圾问题日益凸显&#xff0c;不仅对环境造成污染&#xff0c;也给城市管理带来了巨大挑战。传统的垃圾分类方式不仅费时费力&#xff0c;而且手工操作容易出现错误&#xff0c;导致垃圾分类效…

探索未来工业自动化的钥匙:OPC UA与AI的融合

文章目录 探索未来工业自动化的钥匙&#xff1a;OPC UA与AI的融合背景&#xff1a;为什么选择OPC UA&#xff1f;OPC UA库简介安装OPC UA库简单的库函数使用方法连接到服务器获取节点读取节点值设置节点值订阅数据变更 库的使用场景工业自动化监控能源管理系统预测性维护 常见问…

L8910 【哈工大_操作系统】CPU管理的直观想法多进程图像用户级线程

L2.1 CPU管理的直观想法 管理CPU -> 引出多进程视图 设置 PC 指针初值为程序在内存中开始的地址&#xff0c;自动取指执行多个程序同时放在内存中&#xff0c;让CPU交替执行&#xff08;并发&#xff1a;程序在读I/O时太慢&#xff0c;CPU空闲&#xff0c;则会去执行其他程序…

Jupyterhub 多用户分析平台在线和离线部署(自定义用户认证)

Jupyterhub 文章目录 Jupyterhub1、简介2、安装配置&#xff08;在线&#xff09;2.1 安装准备2.2 安装jupyterhub2.2 自定义身份验证器2.3 自定义单用户jupyter服务生成器2.4 配置 jupyterhub_config.py2.4 启动服务2.5 登录测试2.5.1 用户登录 http://da.db.com2.5.2 管理界面…

synchronized底层是怎么通过monitor进行加锁的?

一、monitor是什么 monitor叫做对象监视器、也叫作监视器锁&#xff0c;JVM规定了每一个java对象都有一个monitor对象与之对应&#xff0c;这monitor是JVM帮我们创建的&#xff0c;在底层使用C实现的。 ObjectMonitor() {_header;_count ; // 非常重要&#xff0c;表示锁计数…

3 个简单的微分段项目

与许多大型网络安全项目一样&#xff0c;微分段似乎很复杂、耗时且成本高昂。 它涉及管理有关设备间服务连接的复杂细节。 一台 Web 服务器应连接到特定数据库&#xff0c;但不连接到其他数据库&#xff0c;或者负载平衡器应连接到某些 Web 服务器&#xff0c;同时限制与其他…

图解大模型计算加速系列:vLLM源码解析1,整体架构

整个vLLM代码读下来&#xff0c;给我最深的感觉就是&#xff1a;代码呈现上非常干净历练&#xff0c;但是逻辑比较复杂&#xff0c;环环嵌套&#xff0c;毕竟它是一个耦合了工程调度和模型架构改进的巨大工程。 所以在源码解读的第一篇&#xff0c;我想先写一下对整个代码架构…

Golang | Leetcode Golang题解之第449题序列化和反序列化二叉搜索树

题目&#xff1a; 题解&#xff1a; type Codec struct{}func Constructor() (_ Codec) { return }func (Codec) serialize(root *TreeNode) string {arr : []string{}var postOrder func(*TreeNode)postOrder func(node *TreeNode) {if node nil {return}postOrder(node.Le…

java基础 day1

学习视频链接 人机交互的小故事 微软和乔布斯借鉴了施乐实现了如今的图形化界面 图形化界面对于用户来说&#xff0c;操作更加容易上手&#xff0c;但是也存在一些问题。使用图形化界面需要加载许多图片&#xff0c;所以消耗内存&#xff1b;此外运行的速度没有命令行快 Wi…

针对考研的C语言学习(2019链表大题)

题目解析&#xff1a; 【考】双指针算法&#xff0c;逆置法&#xff0c;归并法。 解析&#xff1a;因为题目要求空间复杂度为O(1)&#xff0c;即不能再开辟一条链表&#xff0c;因此我们只能用变量来整体挪动原链表。 第一步先找出中间节点 typedef NODE* Node; Node find_m…

latex有哪些颜色中文叫什么,Python绘制出来

latex有哪些颜色中文叫什么&#xff0c;Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称&#xff0c;并使用Python打印出来&#xff0c;我们可以先列出常见的预定义颜色名称&#xff0c;然后将它们翻译成中文&#xff0c;并最后用Python打印出来。 步骤 列出…

家庭记账本的设计与实现+ssm(lw+演示+源码+运行)

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;家庭记账本小程序被用户普遍使用&#xff0c;为方便用户能…

MySQL高阶2066-账户余额

目录 题目 准备数据 分析数据 总结 题目 请写出能够返回用户每次交易完成后的账户余额. 我们约定所有用户在进行交易前的账户余额都为0&#xff0c; 并且保证所有交易行为后的余额不为负数。 返回的结果请依次按照 账户&#xff08;account_id), 日期( day ) 进行升序排序…

leetcode_238:除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂…

Conditional Generative Adversarial Nets

条件生成对抗网络 1.生成对抗网络 生成对网络由两个“对抗性”模型组成&#xff1a;一个生成模型 G&#xff0c;用于捕获数据分布&#xff0c;另一个判别模型 D&#xff0c;用于估计样本来自训练数据而不是 G 的概率。G 和 D 都可以是非线性映射函数。 为了学习数据 x 上的生…

设计模式-生成器模式/建造者模式Builder

构建起模式&#xff1a;将一个复杂类的表示与其构造分离&#xff0c;使得相同的构建过程能够得出不同的表示。&#xff08;建造者其实和工厂模式差不多&#xff09; 详细的UML类图 图文说明&#xff1a;距离相同的构建过程 得出不同的展示。此时就用两个类&#xff08;文本生成…