1. 广度优先搜索(BFS)简介
广度优先搜索(BFS)是一种图的遍历算法。它从起点出发,首先访问距离起点最近的所有节点,然后逐层扩展到更远的节点,直到目标节点被找到,或者图中没有更多可访问的节点。
与 深度优先搜索(DFS) 相比,BFS 是“层次”进行搜索的。BFS 的最大特点是能够找到从起点到目标节点的 最短路径(如果路径长度是均匀的)。因此,在一些寻找最短路径或最小步数的题目中,BFS 是非常有效的。
2. 广度优先搜索的应用场景
BFS 在很多实际问题中都有广泛应用,比如:
- 最短路径问题:在无权图中找到从一个节点到另一个节点的最短路径。
- 图的层次遍历:比如层次化显示图的结构。
- 搜索问题:如迷宫求解,最短路径问题。
- 社交网络分析:找出从某个用户到其他用户的最短关系路径。
3. 如何应用BFS
以典型走出迷宫的最少步数为例
目标是寻找一个从 (0, 0)
到 (n-1, m-1)
的最短路径,并且路径只能穿越 ' . '
(表示可以走的区域)。矩阵中的每个格子要么是空白(.
),要么是墙(#
)。
步骤:
- 将起点
(0, 0)
加入队列q
。 - 每次从队列中取出一个节点,并检查它的四个邻居(上下左右)是否在矩阵范围内,且是否可通行(即是
.
)。 - 如果可以通行且尚未访问过,就将该邻居加入队列,标记为已访问。
- 重复此过程,直到到达目标
(n-1, m-1)
,此时返回路径长度(步数)。如果队列为空且没有到达目标,返回-1
,表示无法到达目标。
4. 核心代码解读:
初始化与输入
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
char[][] ch = new char[n][m];
for (int i = 0; i < n; i++) {
ch[i] = sc.next().toCharArray();
}
这里通过 Scanner
获取矩阵的大小 n x m
,然后逐行输入矩阵的每个字符。
BFS 主体:
Queue<int[]> q = new LinkedList<>();
q.add(new int[] { x1, y1 });
visited[x1][y1] = true; // 起点已访问
创建队列 q
存储节点坐标,并标记起点 (x1, y1)
已访问。
逐层遍历:
while (!q.isEmpty()) {
int n = q.size();
for (int j = 0; j < n; j++) {
int[] cur = q.poll(); // 取出队列中的当前节点
int curX = cur[0];
int curY = cur[1];
if (curX == x2 && curY == y2) { // 到达终点
return count;
}
for (int i = 0; i < 4; i++) {
int nextX = curX + dir[i][0];
int nextY = curY + dir[i][1];
if (nextX < 0 || nextY < 0 || nextX >= arr.length || nextY >= arr[0].length) {
continue;
}
if (!visited[nextX][nextY] && arr[nextX][nextY] == '.') {
q.add(new int[] { nextX, nextY });
visited[nextX][nextY] = true;
}
}
}
count++; // 增加步数
}
- 逐层遍历:BFS 每次都会遍历当前层的所有节点,确保每次扩展的是距离起点最短的节点。
- 四个方向:通过
dir
数组,我们可以便捷地计算当前节点的上下左右四个邻居。 - 队列操作:通过
q.poll()
从队列中取出当前节点,检查其所有可访问的邻居,未访问过且能通过的邻居加入队列。 - 计数器
count
:每完成一层遍历,步数count
加 1,表示我们在路径上走了一步。
5. BFS 完成后返回的结果
return -1; // 如果无法到达目标
如果队列为空时仍未找到目标,则返回 -1
,表示没有路径可达目标。
完整代码:
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main {
// 四个方向:上、右、下、左
public static int[][] dir = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } };
public static int count = 1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
char[][] ch = new char[n][m];
for (int i = 0; i < n; i++) {
ch[i] = sc.next().toCharArray(); // 输入每行的字符串并转换为字符数组
}
boolean[][] visited = new boolean[n][m]; // 初始化访问数组
int ans = bfs(ch, visited, 0, 0, n - 1, m - 1); // 从(0, 0)到(n-1, m-1)找路径
System.out.println(ans);
sc.close();
}
public static int bfs(char[][] arr, boolean[][] visited, int x1, int y1, int x2, int y2) {
Queue<int[]> q = new LinkedList<>();
q.add(new int[] { x1, y1 });
visited[x1][y1] = true; // 起点已访问
while (!q.isEmpty()) {
int n = q.size();
for (int j = 0; j < n; j++) {
int[] cur = q.poll(); // 取出队列中的当前节点
int curX = cur[0];
int curY = cur[1];
if (curX == x2 && curY == y2) { // 到达终点
return count;
}
// 检查四个方向
for (int i = 0; i < 4; i++) {
int nextX = curX + dir[i][0];
int nextY = curY + dir[i][1];
if (nextX < 0 || nextY < 0 || nextX >= arr.length || nextY >= arr[0].length) {
continue;
}
if (!visited[nextX][nextY] && arr[nextX][nextY] == '.') {
q.add(new int[] { nextX, nextY });
visited[nextX][nextY] = true;
}
}
}
count++; // 增加步数
}
return -1; // 如果无法到达目标
}
}
6. BFS优化
虽然在本题中使用 BFS 是直接有效的,但在实际应用中,BFS 的效率可以通过以下几个方向进一步优化:
-
提前终止:
- 当找到目标节点时,立即返回结果,避免不必要的计算。
-
双向 BFS:
- 如果图比较大,可以使用双向 BFS 来加速搜索。即同时从起点和终点开始搜索,并在中途交汇,这样可以减少搜索空间。
-
优先队列(A*算法):
- 如果存在启发式信息(如距离目标的估计值),可以使用优先队列来替代普通队列,这样会进一步提高效率,类似于 A 算法*。
-
多线程并行化:
- 如果图非常大,可以使用多线程并行化 BFS 的计算,分工处理不同区域的搜索,但这通常需要更多的内存和同步机制。
7. 常见问题与边界条件
在实现 BFS 时,要特别注意以下几个常见的边界条件:
- 图的边界:要确保在访问相邻节点时,不越界。
- 障碍物和可达区域:确保在检查邻居时,仅考虑没有障碍物的区域(例如
.
)。 - 起点与终点的特殊情况:如果起点和终点是同一个位置,应该返回 0(因为不需要移动)。
- 无法到达目标:如果没有路径到达目标,返回 -1。
8. 扩展问题:迷宫求解
这个问题的一个经典扩展是 迷宫求解,在迷宫中,起点和终点之间的最短路径通常需要使用 BFS 来求解。对于迷宫求解问题,可以在 BFS 中记录路径的每一步,并在到达目标时返回路径。
public static List<int[]> bfs(char[][] maze, boolean[][] visited, int startX, int startY, int endX, int endY) {
Queue<int[]> q = new LinkedList<>();
List<int[]> path = new ArrayList<>();
q.add(new int[] { startX, startY });
visited[startX][startY] = true;
while (!q.isEmpty()) {
int[] cur = q.poll();
int curX = cur[0];
int curY = cur[1];
if (curX == endX && curY == endY) {
// 可以在此处跟踪路径
return path;
}
// 持续记录路径
for (int i = 0; i < 4; i++) {
int nextX = curX + dir[i][0];
int nextY = curY + dir[i][1];
if (nextX < 0 || nextY < 0 || nextX >= maze.length || nextY >= maze[0].length || visited[nextX][nextY] || maze[nextX][nextY] == '#') {
continue;
}
visited[nextX][nextY] = true;
q.add(new int[] { nextX, nextY });
path.add(new int[] { nextX, nextY });
}
}
return null; // 没有找到路径
}
9. 总结
广度优先搜索(BFS)是图搜索算法中一种重要的策略,尤其适合用来解决从起点到目标的最短路径问题。BFS 保证了每次访问的节点是离起点最近的节点,因此非常适合用于迷宫求解、最短路径问题等场景。在实际应用中,BFS 可以进一步优化,提高搜索效率,并应用于更复杂的问题中。