广度优先搜索(BFS)算法:原理、应用与 Java 实现

广度优先搜索(Breadth-First Search,BFS)是一种经典的图遍历算法,广泛应用于最短路径求解、迷宫问题、社交网络分析等领域。本文将深入探讨 BFS 的原理、实现方式及其应用场景,并通过 Java 代码示例进行详细说明。

目录

一、BFS 算法原理

(一)基本思想

(二)算法流程

(三)迭代实现

二、BFS 的应用场景

(一)图的遍历

(二)最短路径求解

(三)迷宫问题

三、BFS 的 Java 实现

(一)迭代实现

四、BFS 应用例题1:迷宫中离入口最近的出口

(一)问题描述

(二)BFS 解题思路

(三)代码实现

(四)代码解析

(五)复杂度分析

五、BFS 应用2:公交路线

(一)问题描述

(二)BFS 解题思路

(三)代码实现

(四)代码解析

(五)复杂度分析

六、BFS 的优缺点

(一)优点

(二)缺点

六、总结


一、BFS 算法原理

(一)基本思想

BFS 的核心思想是从一个起始节点开始,逐层访问其邻接节点,直到找到目标节点或访问完所有可达节点为止。它按照“先进先出”的原则进行遍历,通常使用队列来实现。

(二)算法流程

  1. 选择起始节点:从图中的某个节点开始,将其标记为已访问,并加入队列。

  2. 逐层访问:每次从队列中取出一个节点,访问其所有未访问的邻接节点,并将这些邻接节点标记为已访问,然后加入队列。

  3. 重复步骤:继续从队列中取出节点,访问其邻接节点,直到队列为空。

(三)迭代实现

BFS 通常使用迭代实现,通过队列来管理待访问的节点。每次从队列中取出一个节点,访问其邻接节点,并将这些邻接节点加入队列。

 

二、BFS 的应用场景

(一)图的遍历

BFS 可以用于遍历图中的所有节点,判断图是否连通。例如,通过 BFS 可以找到图中的所有连通分量。

(二)最短路径求解

BFS 是求解无权图最短路径的经典算法。它能够找到从起点到终点的最短路径,适用于迷宫求解、社交网络中的最短路径等问题。

(三)迷宫问题

在迷宫问题中,BFS 可以用于找到从入口到出口的最短路径。例如,LeetCode 上的题目“1926. 迷宫中离入口最近的出口”就是一个典型的 BFS 应用场景。

三、BFS 的 Java 实现

(一)迭代实现

以下是使用迭代实现 BFS 的 Java 代码示例,代码中添加了详细注释以便理解:

import java.util.*;

// 广度优先搜索(BFS)的迭代实现类
public class BFSIterative {
    // visited 数组用于标记节点是否被访问过,防止重复访问
    private boolean[] visited;
    // graph 是邻接表表示的图,存储每个节点的邻接节点列表
    private List<List<Integer>> graph;

    // 构造函数,初始化图和访问标记数组
    public BFSIterative(int n) {
        // 初始化 visited 数组,所有节点初始为未访问
        visited = new boolean[n];
        // 初始化图的邻接表
        graph = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            // 为每个节点创建一个空的邻接节点列表
            graph.add(new ArrayList<>());
        }
    }

    // 添加边的方法,用于构建图
    public void addEdge(int u, int v) {
        // 无向图:添加 u 到 v 的边,同时添加 v 到 u 的边
        // 有向图:只添加 u 到 v 的边
        // 此处假设是有向图
        graph.get(u).add(v);
    }

    // BFS 遍历方法
    public void bfsTraversal(int start) {
        // 使用队列实现 BFS 的迭代过程
        Queue<Integer> queue = new LinkedList<>();
        // 标记起始节点为已访问
        visited[start] = true;
        // 将起始节点加入队列
        queue.add(start);

        // 当队列不为空时,继续遍历
        while (!queue.isEmpty()) {
            // 从队列中取出一个节点
            int node = queue.poll();
            // 输出当前节点
            System.out.print(node + " ");
            // 遍历当前节点的所有邻接节点
            for (int neighbor : graph.get(node)) {
                // 如果邻接节点未被访问过
                if (!visited[neighbor]) {
                    // 标记为已访问
                    visited[neighbor] = true;
                    // 将邻接节点加入队列,以便后续访问其邻接节点
                    queue.add(neighbor);
                }
            }
        }
    }

    // 主方法,用于测试 BFS 遍历
    public static void main(String[] args) {
        // 创建一个包含 6 个节点的图
        BFSIterative bfs = new BFSIterative(6);
        // 添加边,构建图的结构
        bfs.addEdge(0, 1);
        bfs.addEdge(0, 2);
        bfs.addEdge(1, 3);
        bfs.addEdge(2, 4);
        bfs.addEdge(3, 5);
        bfs.addEdge(4, 5);
        // 输出 BFS 遍历的结果
        System.out.println("BFS Traversal:");
        bfs.bfsTraversal(0);
    }
}

 

四、BFS 应用例题1:迷宫中离入口最近的出口

力扣1926.迷宫中离入口最近的出口https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/solutions/3614228/mi-gong-zhong-chi-ru-kou-zui-jin-de-chu-kvguw

(一)问题描述

给定一个迷宫,迷宫由一个二维数组 maze 表示,其中 '.' 表示可以通行的路径,'#' 表示墙壁。从入口出发,找到离入口最近的出口(出口在迷宫的边界上)。返回从入口到最近出口的最短步数,如果无法到达出口,则返回 -1

(二)BFS 解题思路

BFS 是解决迷宫问题的经典方法。从入口开始,逐层扩展,直到找到一个位于迷宫边界的点,即为最近的出口。

(三)代码实现

以下是使用 BFS 解决“迷宫中离入口最近的出口”问题的 Java 代码示例,代码中每一行都添加了详细注释:

import java.util.*;

public class NearestExitFromEntranceInMaze {
    private static final int[] dx = {0, 0, 1, -1};
    private static final int[] dy = {1, -1, 0, 0};

    public int nearestExit(char[][] maze, int[] entrance) {
        int rows = maze.length;
        int cols = maze[0].length;
        boolean[][] visited = new boolean[rows][cols];
        Queue<int[]> queue = new LinkedList<>();
        
        // 从入口开始
        queue.add(new int[]{entrance[0], entrance[1]});
        visited[entrance[0]][entrance[1]] = true;
        int steps = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] current = queue.poll();
                int x = current[0];
                int y = current[1];

                // 如果当前点不是入口且在边界上,返回步数
                if ((x == 0 || x == rows - 1 || y == 0 || y == cols - 1) && (x != entrance[0] || y != entrance[1])) {
                    return steps;
                }

                // 遍历四个方向
                for (int j = 0; j < 4; j++) {
                    int newX = x + dx[j];
                    int newY = y + dy[j];

                    // 检查是否可以通行且未访问
                    if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && maze[newX][newY] == '.' && !visited[newX][newY]) {
                        queue.add(new int[]{newX, newY});
                        visited[newX][newY] = true;
                    }
                }
            }
            steps++; // 每扩展一层,步数加1
        }
        return -1; // 如果无法到达出口,返回-1
    }

    public static void main(String[] args) {
        char[][] maze = {
            {'+', '+', '.', '+'},
            {'.', '.', '.', '+'},
            {'+', '+', '+', '.'}
        };
        int[] entrance = {1, 2};
        NearestExitFromEntranceInMaze solution = new NearestExitFromEntranceInMaze();
        System.out.println("Nearest Exit Distance: " + solution.nearestExit(maze, entrance));
    }
}

(四)代码解析

  1. 方向数组

    • dxdy 用于表示上下左右四个方向的移动。

  2. 队列初始化

    • 从入口开始,将入口加入队列,并标记为已访问。

  3. 逐层扩展

    • 每次从队列中取出一个点,检查其是否在边界上且不是入口。如果是,则返回当前步数。

    • 否则,遍历其四个方向,将可以通行且未访问的点加入队列,并标记为已访问。

  4. 步数计算

    • 每扩展一层,步数加 1,直到队列为空。

(五)复杂度分析

  • 时间复杂度:O(m×n),其中 m 和 n 分别是迷宫的行数和列数。每个单元格最多被访问一次。

  • 空间复杂度:O(m×n),最坏情况下,队列可能包含整个迷宫的节点。


 

五、BFS 应用2:公交路线

力扣815.公交路线https://leetcode.cn/problems/bus-routes/solutions/3638580/gong-jiao-lu-xian-java-bfsjie-fa-by-xion-4lsx

(一)问题描述

给定一个公交路线的列表 routes,其中每个路线是一个整数数组,表示该路线经过的所有站点。同时给定一个起点 source 和终点 target,返回从起点到终点所需的最少换乘次数。如果无法到达终点,则返回 -1

(二)BFS 解题思路

BFS 是解决此类问题的经典方法。通过将每个公交路线视为一个节点,两个节点之间有边当且仅当它们有共同的站点。从起点所在的路线开始,逐层扩展,直到找到终点所在的路线。

(三)代码实现

以下是使用 BFS 解决“公交路线”问题的 Java 代码示例,代码中每一行都添加了详细注释:

import java.util.*;

public class BusRoutes {
    public int numBusesToDestination(int[][] routes, int source, int target) {
        if (source == target) {
            return 0; // 如果起点和终点相同,无需换乘
        }

        // 构建站点到路线的映射
        Map<Integer, List<Integer>> stationToRoutes = new HashMap<>();
        for (int i = 0; i < routes.length; i++) {
            for (int station : routes[i]) {
                stationToRoutes.putIfAbsent(station, new ArrayList<>());
                stationToRoutes.get(station).add(i);
            }
        }

        // BFS 初始化
        Queue<Integer> queue = new LinkedList<>();
        Set<Integer> visitedRoutes = new HashSet<>();
        Set<Integer> visitedStations = new HashSet<>();

        // 从起点出发
        queue.add(source);
        visitedStations.add(source);
        int transfers = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int currentStation = queue.poll();

                // 遍历当前站点的所有路线
                for (int route : stationToRoutes.getOrDefault(currentStation, Collections.emptyList())) {
                    if (visitedRoutes.contains(route)) {
                        continue; // 如果该路线已经访问过,跳过
                    }
                    visitedRoutes.add(route);

                    // 遍历该路线上的所有站点
                    for (int nextStation : routes[route]) {
                        if (nextStation == target) {
                            return transfers + 1; // 找到终点,返回换乘次数
                        }
                        if (!visitedStations.contains(nextStation)) {
                            queue.add(nextStation);
                            visitedStations.add(nextStation);
                        }
                    }
                }
            }
            transfers++; // 每扩展一层,换乘次数加1
        }
        return -1; // 如果无法到达终点,返回-1
    }

    public static void main(String[] args) {
        int[][] routes = {
            {1, 2, 7},
            {3, 6, 7}
        };
        int source = 1;
        int target = 6;
        BusRoutes solution = new BusRoutes();
        System.out.println("Minimum Transfers: " + solution.numBusesToDestination(routes, source, target));
    }
}

(四)代码解析

  1. 站点到路线的映射

    • 使用 stationToRoutes 将每个站点映射到它所在的路线。这样可以快速找到从一个站点出发可以到达的所有路线。

  2. BFS 初始化

    • 使用队列 queue 存储当前访问的站点。

    • 使用 visitedRoutesvisitedStations 分别记录已经访问过的路线和站点,避免重复访问。

  3. 逐层扩展

    • 每次从队列中取出一个站点,找到该站点可以到达的所有路线。

    • 对于每条路线,遍历其上的所有站点,如果找到终点则返回当前换乘次数。

    • 将未访问过的站点加入队列,并标记为已访问。

  4. 换乘次数计算

    • 每扩展一层,换乘次数加 1,直到队列为空。

(五)复杂度分析

  • 时间复杂度:O(N + S),其中 N 是路线的数量,S 是所有站点的总数。每个站点和路线最多被访问一次。

  • 空间复杂度:O(N + S),用于存储站点到路线的映射以及访问记录。


六、BFS 的优缺点

(一)优点

  1. 保证最短路径:BFS 能够找到无权图中的最短路径。

  2. 实现简单:迭代实现的代码简洁明了。

  3. 适用于大规模数据:对于大规模图或树结构,BFS 可以有效地找到目标。

(二)缺点

  1. 空间复杂度高:在稠密图中,队列可能会占用大量空间。

  2. 效率较低:在某些情况下,BFS 的时间复杂度可能较高。


六、总结

广度优先搜索(BFS)是一种重要的图遍历算法,具有广泛的应用场景。通过迭代实现,BFS 可以有效地探索图中的所有节点。在竞赛中,BFS 也常用于解决迷宫问题,如“迷宫中离入口最近的出口”问题。希望本文对大家学习和理解广度优先搜索算法有所帮助。如果有任何问题或建议,欢迎在评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值