广度优先搜索(BFS)故事:从迷宫探险到社交网络

82 阅读4分钟

一、故事场景:迷宫中的逐层探索

小明被困在一个复杂的迷宫里,他需要找到出口。迷宫由许多房间组成,每个房间都有通向相邻房间的门。小明想到了一个聪明的策略:

  1. 标记当前位置:在每个走过的房间墙上画个叉

  2. 逐层探索:先尝试从当前房间能直接到达的所有相邻房间(第一层)

  3. 记录待探索房间:把每个相邻房间的门牌号写在纸上(队列)

  4. 处理下一层:当第一层的所有房间都探索完后,从队列中取出下一批房间,重复步骤 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 的执行过程详解

  1. 初始化

    • 将起点(0,0)加入队列
    • 标记起点为已访问
  2. 第一轮循环

    • 取出队列头部(0,0)
    • 探索四个方向:右(0,1)是墙,下(1,0)是通路
    • (1,0)加入队列并标记为已访问
  3. 第二轮循环

    • 取出队列头部(1,0)
    • 探索四个方向:上(0,0)已访问,右(1,1)是墙,下(2,0)是通路
    • (2,0)加入队列并标记为已访问
  4. 第三轮循环

    • 取出队列头部(2,0)
    • 探索四个方向:上(1,0)已访问,右(2,1)是通路,下(3,0)是通路
    • (2,1)(3,0)加入队列并标记为已访问
  5. 重复上述过程,直到找到终点或队列为空

五、BFS vs DFS:不同的搜索策略

维度广度优先搜索(BFS)深度优先搜索(DFS)
数据结构队列(Queue)栈(递归或显式栈)
搜索顺序逐层扩展,先近后远一条路走到黑,回溯再探索
适用场景最短路径、最小步数问题所有路径探索、连通性检查
空间复杂度O (V)(最坏情况下存储整层节点)O (H)(H 为最大深度)

六、BFS 的其他应用场景

  1. 社交网络:查找两个人之间的最短关系链(如微信好友推荐)
  2. 层序遍历:二叉树的层序遍历(逐层打印节点)
  3. 游戏 AI:寻找迷宫或地图中的最短路径
  4. 网络爬虫:逐层抓取网页(先抓取当前页面的所有链接,再处理下一层)

七、总结:BFS 的核心思想

BFS 就像在迷宫中逐层探索:

  1. 逐层扩展:从起点开始,先探索所有距离为 1 的节点,再探索距离为 2 的节点,以此类推

  2. 队列管理:用队列存储待探索的节点,确保先处理近的节点

  3. 最短路径:由于逐层扩展的特性,BFS 找到的路径一定是最短路径

掌握 BFS,就像掌握了在复杂网络中寻找最近目标的魔法,是算法学习的重要基础。