BFS (Java) 广度优先搜索 简单介绍、模板、案例(一)

一. BFS的简单介绍

        深度优先搜索DFS和广度优先搜索BFS是经常使用的搜索算法,在各类题目中都有广泛的应用。

        深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。
        广度优先搜索算法(Breadth-First Search,缩写为 BFS),又称为宽度优先搜索,是一种图形搜索算法。简单的说,BFS 是从根结点开始,沿着树的宽度遍历树的结点。如果所有结点均被访问,则算法中止。

        一般来说,能用DFS的,一般都可以用BFS解决,反过来同理。但是不同的题目,对于DFS和BFS的复杂度可能是有些差别的,对于有些题目,用BFS更容易解决,会有更少的时间复杂度。

二. BFS模板

        BFS的载体一般选择双端队列,两边都可以增删,可以满足一些特殊的情况,比如0-1BFS(Ref.[1]).

        一般在循环体内都有另一层循环,此层循环是同层循环,一般在同层循环中,答案的更新是一样的,可以理解为树的同一层,具有同样的深度

class Solution {
    public BFS(TreeNode root) {
        //双端队列,用来存储元素
        Deque<TreeNode> queue = new ArrayDeque<>();
        //添加首个元素
        queue.add(首个元素);
        //当队列不为空一直进行循环,直到队列不再有元素
        while(!queue.isEmpty()){
            int n = queue.size();

            //得到队列的大小
            
            for(int i = 0; i < n; i++){

                var t = queue.poll();

                在同一层的操作;
                ...

            }
            在非同层更新答案;        
        }
        返回答案;
    }
}

三. 典型案例

1.leetcode102 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

 

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        Deque<TreeNode> queue = new ArrayDeque<>();
        if(root != null){
            queue.add(root);
        }
        while(!queue.isEmpty()){
            List<Integer> list = new ArrayList<>();
            int n = queue.size();
            for(int i = 0; i < n; i++){
                TreeNode node = queue.poll();
                 list.add(node.val);
                if(node.left != null){
                    queue.add(node.left);  
                }
                if(node.right != null){
                    queue.add(node.right);
                }
            }
            ans.add(list);
        }
        return ans;
    }
}

本题小结:(1)此题是BFS的典型题目, 在同层添加左右子节点,同层节点具有相同的深度

               (2)以颜色来区分同层,可以得到表示更为清晰的二叉树的层序遍历:

那么,原本的二叉树被分为三层,第一层{3},第二层{9,20},第三层{15,17},同一层内的操作在for循环中完成

2.leetcode 1091 二进制矩阵中的最短路径

给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。

二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:

路径途经的所有单元格的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。

输入:grid = [[0,1],[1,0]]
输出:2

 

class Solution {
    public int shortestPathBinaryMatrix(int[][] grid) {
        if(grid[0][0] == 1) return -1; 
        Deque<Integer> q = new LinkedList<>();
        int m = grid.length;
        int n = grid[0].length;
        boolean[] vis = new boolean[m*n];
        q.addLast(0);
        vis[0] = true;
        int ans = 0;
        while(!q.isEmpty()){
            int size = q.size();
            for(int k = 0; k < size; k++){
                int t = q.pollFirst();
                if(t == m*n-1) return ans+1;
                int x = t/n;
                int y = t%n;
                for(int i = -1; i <= 1; i++){
                    for(int j = -1; j <=1; j++){
                        int xx = x+i;
                        int yy = y+j;
                        if(xx >= m || yy >= n || xx < 0 || yy < 0 || grid[xx][yy] == 1) continue;
                        if(vis[xx*n+yy]) continue;
                        q.addLast(xx*n+yy);
                        vis[xx*n+yy] = true;
                    }
                }
            }
            ans++;
        }
        return -1;
    }
}

本题小结:(1)此题也是BFS的典型题目,此题经典的解法即为BFS或者DFS,类似的还有走迷宫的题目,在矩阵或者图中,从起点到终点的题目几乎都可以用DFS或者BFS来解决。

               (2)在同一层具有相同的深度,即在for循环内ans是相同的,在for循环外来更新ans。

               (3)来到当前点的下一个点的方向可以朝着上下左右行走,那么原本最初的for循环变为两层,但逻辑都是一样的,即:寻找下一步可以走的所有可能性。 

               (4)vis[xx*n+yy] = true;走过的进行标记,这在回溯等方法中也经常使用,在DFS、BFS此类暴力搜索的方法中也不不得不用的措施。

3.leetcode 1210 穿过迷宫的最少移动次数

你还记得那条风靡全球的贪吃蛇吗?

我们在一个 n*n 的网格上构建了新的迷宫地图,蛇的长度为 2,也就是说它会占去两个单元格。蛇会从左上角((0, 0) 和 (0, 1))开始移动。我们用 0 表示空单元格,用 1 表示障碍物。蛇需要移动到迷宫的右下角((n-1, n-2) 和 (n-1, n-1))。

每次移动,蛇可以这样走:

如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 90 度。蛇从((r, c)、(r, c+1))移动到 ((r, c)、(r+1, c))。

如果它处于竖直状态并且其右面的两个单元都是空的,就逆时针旋转 90 度。蛇从((r, c)、(r+1, c))移动到((r, c)、(r, c+1))。

 

返回蛇抵达目的地所需的最少移动次数。

如果无法到达目的地,请返回 -1。

输入:grid = [[0,0,0,0,0,1],
               [1,1,0,0,1,0],
               [0,0,0,0,1,1],
               [0,0,1,0,1,0],
               [0,1,1,0,0,0],
               [0,1,1,0,0,0]]
输出:11
解释:
一种可能的解决方案是 [右, 右, 顺时针旋转, 右, 下, 下, 下, 下, 逆时针旋转, 右, 下]。

 

class Solution {
    int r;
    int c;
    int[][] grid;
    public int minimumMoves(int[][] grid) {
        this.grid = grid;
        r = grid.length;
        c = grid[0].length;
        int ans = 0;
        int endL = r*c - 2;
        int endF = r*c - 1;
        Deque<int[]> q = new ArrayDeque<>();
        boolean[][] vis = new boolean[r*c][r*c];
        //            尾巴 头 别反了
        q.add(new int[]{0,1});
        vis[0][1] = true;
        while(!q.isEmpty()){
            for(int k = q.size(); k > 0; k--){
            var t = q.pollFirst();
            int tF = t[1];
            int tL = t[0];
            if(tF == endF && tL == endL){
                return ans;
            }
            int tFx = t[1]/c;//头x
            int tFy = t[1]%c;//头y
            int tLx = t[0]/c;//尾x
            int tLy = t[0]%c;//尾y
            //水平状态 向右平移一格 (tFx,tFy+1) (tLx,tLy+1)
            //垂直状态 向右平移一个 (tFx,tFy+1) (tLx,tLy+1)
            if(tFx == tLx){//当前是水平状态
                if(tFy+1 < c && grid[tFx][tFy+1] == 0 && !vis[tLx*c+tLy+1][tFx*c+tFy+1]){//水平向右
                    q.offerLast(new int[]{tLx*c+tLy+1,tFx*c+tFy+1});
                    vis[tLx*c+tLy+1][tFx*c+tFy+1] = true;
                }
                if(tFx+1 < r && grid[tFx+1][tFy] == 0 && grid[tLx+1][tLy] == 0){
                    if(!vis[(tLx+1)*c+tLy][(tFx+1)*c+tFy]){//水平向下
                        q.offerLast(new int[]{(tLx+1)*c+tLy,(tFx+1)*c+tFy});
                        vis[(tLx+1)*c+tLy][(tFx+1)*c+tFy] = true;
                    }
                    if(!vis[tLx*c+tLy][(tFx+1)*c+tFy-1]){//右旋转
                        q.offerLast(new int[]{tLx*c+tLy,(tFx+1)*c+tFy-1});
                        vis[tLx*c+tLy][(tFx+1)*c+tFy-1] = true;
                    }
                }
            }
            else{//当前是垂直状态
                if(tFx+1 < r && grid[tFx+1][tFy] == 0 && !vis[(tLx+1)*c+tLy][(tFx+1)*c+tFy]){//水平向下
                    q.offerLast(new int[]{(tLx+1)*c+tLy,(tFx+1)*c+tFy});
                    vis[(tLx+1)*c+tLy][(tFx+1)*c+tFy] = true;
                }
                if(tFy+1 < c && grid[tFx][tFy+1] == 0 && grid[tLx][tLy+1] == 0){
                    if(!vis[tLx*c+tLy+1][tFx*c+tFy+1]){//水平向右
                        q.offerLast(new int[]{tLx*c+tLy+1, tFx*c+tFy+1});
                        vis[tLx*c+tLy+1][tFx*c+tFy+1] = true;
                    }
                    if(!vis[tLx*c+tLy][(tFx-1)*c+tFy+1]){//左旋转
                        q.offerLast(new int[]{tLx*c+tLy,(tFx-1)*c+tFy+1});
                        vis[tLx*c+tLy][(tFx-1)*c+tFy+1] = true;
                    }
                }
            }
            }
            ans++;
        }
        return -1;
    }
}

本题小结:(1)此题是走迷宫类的题目,和上述题目不同,走的方式有一定的限制,但是总体的额方法是类似的

               (2)本解法采用水平,垂直分类处理,会出现较多的if-else,非常不美观,容易出错,但是可以明确大体思路,更简便的做法可参考Ref.[2]

参考来源 Ref.

[1] 灵茶山艾府  还在 if-else?一个循环处理六种移动!(Python/Java/C++/Go)

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java广度优先搜索BFS)是一种图遍历算法,用于在一个图中从给定的起始节点开始,按照广度优先的顺序遍历所有可达节点。BFS通常使用队列来实现。 以下是Java实现广度优先搜索的基本步骤: 1. 创建一个队列,并将起始节点入队。 2. 创建一个HashSet或HashMap来记录已访问的节点,防止重复访问。 3. 进入循环,直到队列为空: - 从队列中取出一个节点作为当前节点。 - 将当前节点标记为已访问。 - 处理当前节点,例如输出节点值或进行其他操作。 - 将当前节点的所有未访问过的邻居节点入队。 4. 循环结束后,遍历完成。 下面是一个简单Java代码示例,演示了如何使用BFS遍历一个图: ```java import java.util.*; public class BFS { public void bfsTraversal(Graph graph, int startNode) { Queue<Integer> queue = new LinkedList<>(); Set<Integer> visited = new HashSet<>(); queue.add(startNode); visited.add(startNode); while (!queue.isEmpty()) { int currentNode = queue.poll(); System.out.print(currentNode + " "); List<Integer> neighbors = graph.getNeighbors(currentNode); for (int neighbor : neighbors) { if (!visited.contains(neighbor)) { queue.add(neighbor); visited.add(neighbor); } } } } public static void main(String[] args) { Graph graph = new Graph(6); graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(1, 3); graph.addEdge(1, 4); graph.addEdge(2, 4); graph.addEdge(3, 5); graph.addEdge(4, 5); BFS bfs = new BFS(); bfs.bfsTraversal(graph, 0); } } class Graph { private int vertices; private List<List<Integer>> adjacencyList; public Graph(int vertices) { this.vertices = vertices; adjacencyList = new ArrayList<>(vertices); for (int i = 0; i < vertices; i++) { adjacencyList.add(new ArrayList<>()); } } public void addEdge(int source, int destination) { adjacencyList.get(source).add(destination); adjacencyList.get(destination).add(source); } public List<Integer> getNeighbors(int vertex) { return adjacencyList.get(vertex); } } ``` 这段代码中,我们创建了一个Graph类来表示图,使用邻接表来存储图的结构。然后,我们使用BFS类中的bfsTraversal方法来执行广度优先搜索,并传入起始节点的索引。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值