BFS(广度优先搜索)和DFS(深度优先搜索)总是会被一起提及。在实际应用中,我们用DFS的时候远远多于BFS,那么什么使用场景是DFS不能够做到的,只能使用BFS遍历:层序遍历、最短路径
- BFS与DFS,我们先看看在二叉树上进行DFS遍历和BFS遍历的代码比较
BFS遍历使用递归:
void dfs(TreeNode root){
if(root==null){
return ;
}
dfs(root.left);
dfs(root.right);
}
BFS遍历使用队列(先进先出)数据结构:
void bfs(TreeNode root){
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
}
比较两段代码的话,可以看出DFS的代码更整洁,空间复杂度更低,所以对于树或者图简单的遍历我们一般会选择使用DFS。
2. 虽然DFS和BFS都是将二叉树的节点遍历了一遍,但他们遍历节点的顺序不同:
BFS这个遍历顺序是能够用来解层序遍历、最短路径问题的根本原因,下面结合几道算法题来讲讲具体怎么实现的。
BFS应用1——层序遍历
解题思路:
- 什么是层序遍历?结合二叉树来看,层序遍历就是把二叉树分层,然后每一层从左到右遍历,这个遍历顺序和BFS遍历顺序一致。
- 但是层序遍历要求我们区分每一层,也就是返回一个二维数组,而 BFS 的遍历结果是一个一维数组,无法区分每一层。
- 因此我们需要稍微修改一下代码,在每一层遍历之前,先记录队列中节点数量n(也就是这一层的结点数),然后一口气处理完这一层的n个节点。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
// 最终的返回队列
List<List<Integer>> list = new ArrayList<>();
Queue<TreeNode> queue = new ArrayDeque<>();
if(root!=null){
queue.add(root);
}
while(!queue.isEmpty()){
// 存储不同层的节点
List<Integer> list1 = new ArrayList<>();
// 在每一层遍历开始前,先记录队列中的结点数量 n
int n = queue.size();
// 一口气处理完这一层的 n 个结点
for(int i=0;i<n;i++){
TreeNode node = queue.poll();
// 把该层节点的值加入到队列中
list1.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
// 每一层的遍历结果放入队列
list.add(list1);
}
return list;
}
}
BFS应用2——最短路径
解题思路:
- 在二叉树中,BFS可以实现一层一层的遍历,在图中同样如此。从源点除法,BFS首先遍历到第一层节点,到源点的距离为1,然后遍历到第二层节点,到源点距离为2…可以看到,用BFS的话,距离源点更近的点会被先遍历到,这样就能找到某个点的最短路径了。
- 仿照上面的二叉树层序遍历代码,类似地可以写出网格层序遍历:将遍历过的格子标记为 2,避免重复遍历!!
- 由于一个格子有四个相邻的格子,判断四遍格子坐标的合法性可以优化,使用一个数组存储相邻格子的四个方向(上下左右)
- 假设网格中只有一个陆地格子,我们可以从这个陆地格子出发做层序遍历,直到所有格子都遍历完。最终遍历了几层,海洋格子的最远距离就是几。
- BFS 完全可以以多个格子同时作为起点。我们可以把所有的陆地格子同时放入初始队列,然后开始层序遍历。
//网格结构的层序遍历
//从格子(i,j)开始遍历
public int maxDistance(int[][] grid) {
// 使用数组存储相邻格子的四个方向(上下左右)
int[][]moves = new int [][]{{1,0},{-1,0},{0,-1},{0,1}};
Queue<int[]> queue = new ArrayDeque<>();
// 把所有的陆地格子加入队列,成为多源BFS
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]==1)
queue.add(new int[]{i,j});
}
}
//记录当前遍历的层数
int distance =-1;
while(!queue.isEmpty()){
distance++;
int n = queue.size();
for(int i=0;i<n;i++){
int [] node = queue.poll();
// 横坐标
int r = node[0];
// 纵坐标
int c = node[1];
for(int [] move:moves){
int r2 =r+move[0];
int c2 =c+move[1];
if(r2>=0 && r2<grid.length && c2>=0 && c2<grid[0].length && grid[r2][c2]==0){
// 将遍历过的格子标记为 2
grid[r2][c2] =2;
// 将遍历过的格子加入到队列中,继续向四个方向遍历寻找海洋格子
queue.add(new int[]{r2,c2});
}
}
}
}
}
总结一下广度优先遍历/搜索应用的步骤:
- 第一步:设置队列,添加初始队列节点;
- 第二步:判断队列是否为空;
- 第三步:迭代操作,弹出队列元素,进行逻辑处理,当前队列元素的下级元素,入队;
- 第四步:再次执行步骤三
如果这篇文章对你有帮助的话,就留下一个赞👍吧!