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

【数据结构与算法-Day 16】队列的应用:广度优先搜索(BFS)的基石与迷宫寻路实战

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

Docker系列文章目录

数据结构与算法系列文章目录

01-【数据结构与算法-Day 1】程序世界的基石:到底什么是数据结构与算法?
02-【数据结构与算法-Day 2】衡量代码的标尺:时间复杂度与大O表示法入门
03-【数据结构与算法-Day 3】揭秘算法效率的真相:全面解析O(n^2), O(2^n)及最好/最坏/平均复杂度
04-【数据结构与算法-Day 4】从O(1)到O(n²),全面掌握空间复杂度分析
05-【数据结构与算法-Day 5】实战演练:轻松看懂代码的时间与空间复杂度
06-【数据结构与算法-Day 6】最朴素的容器 - 数组(Array)深度解析
07-【数据结构与算法-Day 7】告别数组束缚,初识灵活的链表 (Linked List)
08-【数据结构与算法-Day 8】手把手带你拿捏单向链表:增、删、改核心操作详解
09-【数据结构与算法-Day 9】图解单向链表:从基础遍历到面试必考的链表反转
10-【数据结构与算法-Day 10】双向奔赴:深入解析双向链表(含图解与代码)
11-【数据结构与算法-Day 11】从循环链表到约瑟夫环,一文搞定链表的终极形态
12-【数据结构与算法-Day 12】深入浅出栈:从“后进先出”原理到数组与链表双实现
13-【数据结构与算法-Day 13】栈的应用:从括号匹配到逆波兰表达式求值,面试高频考点全解析
14-【数据结构与算法-Day 14】先进先出的公平:深入解析队列(Queue)的核心原理与数组实现
15-【数据结构与算法-Day 15】告别“假溢出”:深入解析循环队列与双端队列
16-【数据结构与算法-Day 16】队列的应用:广度优先搜索(BFS)的基石与迷宫寻路实战


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • Python系列文章目录
  • Go语言系列文章目录
  • Docker系列文章目录
  • 数据结构与算法系列文章目录
  • 摘要
  • 一、为什么队列是 BFS 的天然搭档?
    • 1.1 回顾队列:先进先出 (FIFO) 的本质
    • 1.2 引入广度优先搜索 (BFS):地毯式搜索
    • 1.3 思想碰撞:FIFO 与“逐层扩展”的完美契合
  • 二、广度优先搜索 (BFS) 的算法框架
    • 2.1 核心要素
    • 2.2 伪代码实现
  • 三、实战演练:用队列实现迷宫最短路径问题
    • 3.1 问题描述
    • 3.2 将问题转化为图模型
    • 3.3 Java 代码实现
    • 3.4 过程图解
  • 四、BFS 的其他经典应用场景
      • 4.1 无权图单源最短路径
      • 4.2 树的层序遍历
      • 4.3 寻找社交网络中的连接
      • 4.4 操作系统与网络
  • 五、总结


摘要

在前面的章节中,我们学习了队列(Queue)这一“先进先出”(FIFO)的线性数据结构。然而,队列的价值远不止于简单的数据存取。它是许多重要算法的基石,其中最著名、最广泛应用的便是广度优先搜索(Breadth-First Search, BFS)。本文将深入探讨为什么队列是实现 BFS 的不二之选,详细拆解 BFS 的算法框架,并通过经典的“迷宫最短路径”问题进行实战演练,让你彻底掌握这一强大的图遍历算法。学习本章,你将理解算法是如何借助特定数据结构来解决复杂问题的。

一、为什么队列是 BFS 的天然搭档?

在揭示 BFS 的奥秘之前,我们必须先理解它与队列之间密不可分的联系。这种联系源于它们内在思想的高度一致性。

1.1 回顾队列:先进先出 (FIFO) 的本质

我们再次温习队列的核心特性:先进先出(First-In, First-Out)。就像在食堂排队打饭,最先进入队伍的人最先打到饭并离开。

  • 入队 (Enqueue): 新元素总是被添加到队列的尾部。
  • 出队 (Dequeue): 元素总是从队列的头部被移除。

这个特性保证了处理元素的顺序与它们被添加的顺序完全一致。

1.2 引入广度优先搜索 (BFS):地毯式搜索

广度优先搜索(BFS)是一种用于遍历或搜索树或图的算法。顾名思义,“广度优先”意味着算法会尽可能地“横向”扩展,探索完当前层级的所有节点后,才会进入下一层级。

我们可以用一个生动的比喻来理解:

想象一下,你在一个平静的湖面上投下一颗石子,水波会如何扩散?它会以石子落点为中心,形成一个圈,然后这个圈会均匀地向外一圈一圈地扩大。BFS 的搜索方式就如同这水波的扩散,从起点开始,首先访问所有距离为 1 的邻居,然后是所有距离为 2 的邻居,以此类推,逐层向外推进。

下面是一个 BFS 遍历过程的可视化图:

第 2 层
第 1 层
第 0 层
E
F
G
H
B
C
D
起始节点 A

BFS 的访问顺序将是:A -> B -> C -> D -> E -> F -> G -> H

1.3 思想碰撞:FIFO 与“逐层扩展”的完美契合

现在,我们将队列的 FIFO 特性与 BFS 的逐层扩展思想结合起来:

  1. 起点入队: 搜索开始时,我们将起点(第 0 层)放入队列。
  2. 处理第 0 层,发现第 1 层: 我们从队列中取出起点 A。然后,我们找到 A 的所有邻居 BCD(第 1 层),并按顺序将它们加入队列。
  3. 处理第 1 层,发现第 2 层:
    • 此时队列中的元素是 [B, C, D]。根据 FIFO 原则,我们先取出 B,找到其邻居 EF(第 2 层)并入队。
    • 接着取出 C,找到其邻居 G(第 2 层)并入队。
    • 再取出 D,找到其邻居 H(第 2 层)并入队。
  4. 顺序保证: 因为 B, C, D 是在处理 A 时被顺序放入的,所以它们也会被顺序处理。同样,只有当所有第 1 层的节点 (B, C, D) 都被处理完毕后,我们才会开始处理第 2 层的节点 (E, F, G, H)。

结论: 队列的“先进先出”机制天然地保证了 BFS 算法“逐层扩展”的搜索顺序。先发现的节点(更靠近起点的层)会被先处理,其下一层的邻居也会被先于更远层的节点放入队列。这种完美的契合使得队列成为实现 BFS 的标准工具。

二、广度优先搜索 (BFS) 的算法框架

理解了其背后的思想,我们可以构建一个通用的 BFS 算法框架。

2.1 核心要素

要成功实现一个 BFS 算法,通常需要以下两个核心组件:

  1. 一个队列 (Queue): 用于存储待访问的节点,保证逐层遍历的顺序。
  2. 一个记录访问状态的集合 (Set/Boolean Array): 通常称为 visited 集合,用于记录哪些节点已经被访问过或已加入队列。这是至关重要的一步,可以防止算法在图中来回兜圈,陷入无限循环,并避免重复处理节点。

2.2 伪代码实现

将上述流程转化为伪代码,可以更清晰地展示其逻辑结构。

function BFS(startNode, targetNode):// 1. 初始化queue = new Queue()visited = new Set()queue.enqueue(startNode)visited.add(startNode)// 2. 循环直到队列为空while queue is not empty:currentNode = queue.dequeue()// 3. 处理当前节点if currentNode is targetNode:return "找到目标" // 或返回路径// 4. 将未访问的邻居入队for each neighbor in currentNode.getNeighbors():if neighbor is not in visited:visited.add(neighbor)queue.enqueue(neighbor)return "未找到目标"

这个框架是解决所有 BFS 问题的基础模板。

三、实战演练:用队列实现迷宫最短路径问题

理论知识需要通过实践来巩固。让我们来看一个最经典的 BFS 应用:求解迷宫的最短路径。

3.1 问题描述

给定一个 m x n 的二维网格 maze,其中:

  • 0 代表可以通过的路径。
  • 1 代表无法通过的墙壁。

要求从给定的起点 (startX, startY) 走到终点 (endX, endY),找到一条步数最少的路径。如果无法到达,则返回-1。

为什么 BFS 能保证找到最短路径?
因为 BFS 是逐层扩展的,它访问到的所有节点的顺序,就是按照离起点的距离(步数)从近到远来的。所以,当 BFS 第一次到达终点时,所经过的路径必然是所有可能路径中步数最少的一条。这只适用于无权图(即每一步的“代价”都为1),而迷宫问题恰好符合这个模型。

3.2 将问题转化为图模型

在解决问题前,我们需要进行一步关键的思维转换:将迷宫抽象为图。

  • 节点 (Node): 迷宫中每个可以通过的格子 (x, y) 都是图中的一个节点。
  • 边 (Edge): 如果两个格子 (x1, y1)(x2, y2) 相邻(上、下、左、右)且都是通路,那么它们在图中就有一条边连接。

我们的目标就变成了:在这个由迷宫格子构成的图中,找到从 startNodeendNode 的最短路径。

3.3 Java 代码实现

下面我们使用 Java 来实现这个迷宫求解器。

import java.util.LinkedList;
import java.util.Queue;public class MazeSolver {// 用于表示迷宫中的一个点,包含坐标和到达该点的步数static class Point {int x, y, steps;Point(int x, int y, int steps) {this.x = x;this.y = y;this.steps = steps;}}public static int findShortestPath(int[][] maze, int[] start, int[] end) {int m = maze.length;int n = maze[0].length;// 如果起点或终点是墙,则无法到达if (maze[start[0]][start[1]] == 1 || maze[end[0]][end[1]] == 1) {return -1;}// 1. 初始化队列和 visited 数组Queue<Point> queue = new LinkedList<>();boolean[][] visited = new boolean[m][n];// 2. 将起点加入队列,并标记为已访问queue.offer(new Point(start[0], start[1], 0));visited[start[0]][start[1]] = true;// 定义四个方向的移动:上, 下, 左, 右int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};// 3. 开始 BFS 循环while (!queue.isEmpty()) {Point current = queue.poll();// 检查是否到达终点if (current.x == end[0] && current.y == end[1]) {return current.steps; // 第一次到达终点,即为最短路径}// 4. 遍历当前点的四个方向的邻居for (int[] dir : directions) {int nextX = current.x + dir[0];int nextY = current.y + dir[1];// 检查邻居点是否合法(在边界内、是通路、未被访问)if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n &&maze[nextX][nextY] == 0 && !visited[nextX][nextY]) {// 将合法的邻居点标记为已访问visited[nextX][nextY] = true;// 将邻居点加入队列,步数+1queue.offer(new Point(nextX, nextY, current.steps + 1));}}}// 如果队列为空还没找到终点,说明无法到达return -1;}public static void main(String[] args) {int[][] maze = {{0, 0, 1, 0, 0},{0, 0, 0, 0, 0},{0, 0, 0, 1, 0},{1, 1, 0, 1, 1},{0, 0, 0, 0, 0}};int[] start = {0, 0};int[] end = {4, 4};int shortestPath = findShortestPath(maze, start, end);if (shortestPath != -1) {System.out.println("从 (" + start[0] + "," + start[1] + ") 到 (" + end[0] + "," + end[1] + ") 的最短路径长度为: " + shortestPath);} else {System.out.println("无法找到从起点到终点的路径。");}}
}

3.4 过程图解

我们用一个简化的 3x3 迷宫来追踪算法的执行过程。
S 是起点,E 是终点,1 是墙。

迷宫:

S 0 0
0 1 0
0 0 E

visited 数组:

F F F
F F F
F F F

(F=False, T=True)

步骤操作队列内容 queue当前出队 currentvisited 状态更新
0初始化[(0,0,0)]-(0,0) 变为 T
1出队(0,0,0),其邻居(0,1)(1,0)入队[(0,1,1), (1,0,1)](0,0,0)(0,1), (1,0) 变为 T
2出队(0,1,1),其邻居(0,2)入队[(1,0,1), (0,2,2)](0,1,1)(0,2) 变为 T
3出队(1,0,1),其邻居(2,0)入队[(0,2,2), (2,0,2)](1,0,1)(2,0) 变为 T
4出队(0,2,2),无合法邻居[(2,0,2)](0,2,2)
5出队(2,0,2),其邻居(2,1)入队[(2,1,3)](2,0,2)(2,1) 变为 T
6出队(2,1,3),其邻居(2,2)(终点)入队[(2,2,4)](2,1,3)(2,2) 变为 T
7出队(2,2,4)到达终点![](2,2,4)-
-返回 current.steps = 4---

通过这个表格,我们可以清晰地看到队列是如何一步步存储和处理待访问节点,并最终有序地找到终点的。

四、BFS 的其他经典应用场景

除了迷宫问题,BFS 的应用非常广泛,因为它能解决一大类“最短路径”或“层级”相关的问题。

4.1 无权图单源最短路径

这是 BFS 最核心的应用。对于任何不带权重的图,从单个源点 S 出发,BFS 可以找到 S到所有其他可达节点的最短路径长度。

4.2 树的层序遍历

我们在讲二叉树时会详细介绍,遍历一棵树时,如果希望按层级从上到下、从左到右访问所有节点,其实现方法就是 BFS。这在需要按深度处理节点时非常有用。

4.3 寻找社交网络中的连接

在社交网络(如 LinkedIn、Facebook)中,人与人之间的关系可以看作一张图。要查找两个人之间的“最小间隔度数”(例如,A 通过 B 认识 C,间隔为 2),本质上就是在图中寻找两个节点之间的最短路径,BFS 是理想的解决方案。

4.4 操作系统与网络

在某些场景下,如操作系统的资源分配或网络中的广播,需要将信息或任务逐层分发出去,这种模式也蕴含了 BFS 的思想。

五、总结

经过本篇文章的学习,我们深入探索了队列在算法领域的重要应用——广度优先搜索(BFS)。

  1. 核心关联: 我们理解了队列的“先进先出”(FIFO)特性与 BFS“逐层扩展”的搜索策略是完美契合的,这是队列成为实现 BFS 标准工具的根本原因。
  2. 算法框架: 我们掌握了 BFS 的通用算法框架,它由“一个队列”和“一个 visited 集合”两大核心要素构成,并熟悉了其标准的初始化、循环、处理和扩展邻居的流程。
  3. 实战应用: 通过经典的“迷宫最短路径”问题,我们学会了如何将现实问题抽象为图模型,并利用 BFS 模板编写代码来求解。我们还明白了为什么 BFS 能够保证找到无权图中的最短路径。
  4. 知识拓展: 我们了解了 BFS 在树的层序遍历、社交网络分析等多个领域的广泛应用,认识到它是一个解决“最短”和“层级”问题的强大工具。

至此,你不仅巩固了对队列的理解,更开启了图算法的大门。在后续的学习中,你会发现 BFS 是许多更复杂算法的基础。


http://www.dtcms.com/a/336381.html

相关文章:

  • vulnhub-lampiao靶机渗透
  • 002.Redis 配置及数据类型
  • 安装pytorch3d后报和本机cuda不符
  • LLM、RAG、Agent知识点思维导图
  • 简单了解BeanFactory和FactoryBean的区别
  • AMBA-AXI and ACE协议详解(八)
  • Critic-V: VLM Critics Help Catch VLM Errors in Multimodal Reasoning(CVPR 2025)
  • C++零拷贝网络编程实战:从理论到生产环境的性能优化之路
  • Word和Excel的一些功能记录
  • PHP现代化全栈开发:测试驱动开发与持续交付实践
  • 重温k8s基础概念知识系列二(Pod)
  • 腾讯开源:视频生成框架Hunyuan-GameCraft
  • 说一下事件传播机制
  • LeeCode 39.组合总和
  • 第4章 鼎鼎大名的Reactor模式
  • Redis--day7--黑马点评--优惠券秒杀
  • steam_api64.dll丢失?steam_api64.dll修复工具
  • 837. 新 21 点
  • C语言基础:(十六)深入理解指针(6)
  • 在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
  • 基于粒子群优化算法优化支持向量机的数据回归预测 PSO-SVM
  • Java实战:数字转中文大写金额的完整实现与优化技巧
  • 偏最小二乘结构方程(PLS-SEM)_TomatoSCI分析日记
  • bash shell 入门
  • rt-thread audio框架移植stm32 adc+dac,对接cherryusb uac,进行录音和播放
  • RTC之神奇小闹钟
  • 弱类型语言(Strong Typing)与强类型语言(Weak Typing)(描述语言对变量类型处理的严格程度)
  • 【Virtual Globe 渲染技术笔记】7 GPU 光线投射
  • 法拉第笼原理
  • Windows快捷方式添加命令行参数