广度优先搜索(BFS)

1. 广度优先搜索(BFS)简介

广度优先搜索(BFS)是一种图的遍历算法。它从起点出发,首先访问距离起点最近的所有节点,然后逐层扩展到更远的节点,直到目标节点被找到,或者图中没有更多可访问的节点。

深度优先搜索(DFS) 相比,BFS 是“层次”进行搜索的。BFS 的最大特点是能够找到从起点到目标节点的 最短路径(如果路径长度是均匀的)。因此,在一些寻找最短路径或最小步数的题目中,BFS 是非常有效的。

2. 广度优先搜索的应用场景

BFS 在很多实际问题中都有广泛应用,比如:

  • 最短路径问题:在无权图中找到从一个节点到另一个节点的最短路径。
  • 图的层次遍历:比如层次化显示图的结构。
  • 搜索问题:如迷宫求解,最短路径问题。
  • 社交网络分析:找出从某个用户到其他用户的最短关系路径。

3. 如何应用BFS

以典型走出迷宫的最少步数为例

目标是寻找一个从 (0, 0)(n-1, m-1) 的最短路径,并且路径只能穿越 ' . '(表示可以走的区域)。矩阵中的每个格子要么是空白(.),要么是墙(#)。

步骤

  1. 将起点 (0, 0) 加入队列 q
  2. 每次从队列中取出一个节点,并检查它的四个邻居(上下左右)是否在矩阵范围内,且是否可通行(即是 .)。
  3. 如果可以通行且尚未访问过,就将该邻居加入队列,标记为已访问。
  4. 重复此过程,直到到达目标 (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 的效率可以通过以下几个方向进一步优化:

  1. 提前终止:

    • 当找到目标节点时,立即返回结果,避免不必要的计算。
  2. 双向 BFS:

    • 如果图比较大,可以使用双向 BFS 来加速搜索。即同时从起点和终点开始搜索,并在中途交汇,这样可以减少搜索空间。
  3. 优先队列(A*算法):

    • 如果存在启发式信息(如距离目标的估计值),可以使用优先队列来替代普通队列,这样会进一步提高效率,类似于 A 算法*。
  4. 多线程并行化:

    • 如果图非常大,可以使用多线程并行化 BFS 的计算,分工处理不同区域的搜索,但这通常需要更多的内存和同步机制。

7. 常见问题与边界条件

在实现 BFS 时,要特别注意以下几个常见的边界条件:

  1. 图的边界:要确保在访问相邻节点时,不越界。
  2. 障碍物和可达区域:确保在检查邻居时,仅考虑没有障碍物的区域(例如 .)。
  3. 起点与终点的特殊情况:如果起点和终点是同一个位置,应该返回 0(因为不需要移动)。
  4. 无法到达目标:如果没有路径到达目标,返回 -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 可以进一步优化,提高搜索效率,并应用于更复杂的问题中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值