一、故事场景:迷宫中的逐层探索
小明被困在一个复杂的迷宫里,他需要找到出口。迷宫由许多房间组成,每个房间都有通向相邻房间的门。小明想到了一个聪明的策略:
-
标记当前位置:在每个走过的房间墙上画个叉
-
逐层探索:先尝试从当前房间能直接到达的所有相邻房间(第一层)
-
记录待探索房间:把每个相邻房间的门牌号写在纸上(队列)
-
处理下一层:当第一层的所有房间都探索完后,从队列中取出下一批房间,重复步骤 2
这就是 ** 广度优先搜索(BFS)** 的核心思想:逐层扩展,先近后远。
二、BFS 的核心概念
- 队列(Queue) :用于存储待探索的节点,遵循 ** 先进先出(FIFO)** 原则
- 访问标记:记录已访问的节点,避免重复访问
- 逐层扩展:每次处理完当前层的所有节点后,再处理下一层
三、Java 代码实现:迷宫搜索
java
import java.util.LinkedList;
import java.util.Queue;
public class BreadthFirstSearch {
// 迷宫搜索示例
public static void main(String[] args) {
// 迷宫:0表示通路,1表示墙
int[][] maze = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 0, 1, 0}
};
int startX = 0, startY = 0; // 起点坐标
int endX = 4, endY = 4; // 终点坐标
// 调用BFS方法搜索路径
boolean found = bfs(maze, startX, startY, endX, endY);
if (found) {
System.out.println("恭喜!找到出口了!");
} else {
System.out.println("遗憾!没有找到出口。");
}
}
public static boolean bfs(int[][] maze, int startX, int startY, int endX, int endY) {
int rows = maze.length;
int cols = maze[0].length;
// 检查起点或终点是否是墙
if (maze[startX][startY] == 1 || maze[endX][endY] == 1) {
return false;
}
// 创建访问标记数组
boolean[][] visited = new boolean[rows][cols];
// 创建队列存储待探索的节点
Queue<int[]> queue = new LinkedList<>();
// 将起点加入队列并标记为已访问
queue.offer(new int[]{startX, startY});
visited[startX][startY] = true;
// 定义四个方向:上、下、左、右
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// BFS主循环
while (!queue.isEmpty()) {
// 取出队列头部节点
int[] current = queue.poll();
int x = current[0];
int y = current[1];
// 检查是否到达终点
if (x == endX && y == endY) {
return true;
}
// 探索当前节点的所有相邻节点
for (int[] dir : directions) {
int newX = x + dir[0];
int newY = y + dir[1];
// 检查新坐标是否合法且未访问过
if (newX >= 0 && newX < rows && newY >= 0 && newY < cols
&& maze[newX][newY] == 0 && !visited[newX][newY]) {
// 将合法的相邻节点加入队列并标记为已访问
queue.offer(new int[]{newX, newY});
visited[newX][newY] = true;
}
}
}
// 队列为空仍未找到终点,返回失败
return false;
}
}
四、BFS 的执行过程详解
-
初始化:
- 将起点
(0,0)加入队列 - 标记起点为已访问
- 将起点
-
第一轮循环:
- 取出队列头部
(0,0) - 探索四个方向:右
(0,1)是墙,下(1,0)是通路 - 将
(1,0)加入队列并标记为已访问
- 取出队列头部
-
第二轮循环:
- 取出队列头部
(1,0) - 探索四个方向:上
(0,0)已访问,右(1,1)是墙,下(2,0)是通路 - 将
(2,0)加入队列并标记为已访问
- 取出队列头部
-
第三轮循环:
- 取出队列头部
(2,0) - 探索四个方向:上
(1,0)已访问,右(2,1)是通路,下(3,0)是通路 - 将
(2,1)和(3,0)加入队列并标记为已访问
- 取出队列头部
-
重复上述过程,直到找到终点或队列为空
五、BFS vs DFS:不同的搜索策略
| 维度 | 广度优先搜索(BFS) | 深度优先搜索(DFS) |
|---|---|---|
| 数据结构 | 队列(Queue) | 栈(递归或显式栈) |
| 搜索顺序 | 逐层扩展,先近后远 | 一条路走到黑,回溯再探索 |
| 适用场景 | 最短路径、最小步数问题 | 所有路径探索、连通性检查 |
| 空间复杂度 | O (V)(最坏情况下存储整层节点) | O (H)(H 为最大深度) |
六、BFS 的其他应用场景
- 社交网络:查找两个人之间的最短关系链(如微信好友推荐)
- 层序遍历:二叉树的层序遍历(逐层打印节点)
- 游戏 AI:寻找迷宫或地图中的最短路径
- 网络爬虫:逐层抓取网页(先抓取当前页面的所有链接,再处理下一层)
七、总结:BFS 的核心思想
BFS 就像在迷宫中逐层探索:
-
逐层扩展:从起点开始,先探索所有距离为 1 的节点,再探索距离为 2 的节点,以此类推
-
队列管理:用队列存储待探索的节点,确保先处理近的节点
-
最短路径:由于逐层扩展的特性,BFS 找到的路径一定是最短路径
掌握 BFS,就像掌握了在复杂网络中寻找最近目标的魔法,是算法学习的重要基础。