dfs和bfs的讲解、模板和典型例题

1,概述

广度优先搜索(BFS),利用队列实现,特点是水泛涟漪;

深度优先搜索(DFS),使用递归实现(其实是用到了栈的形式,先进后出),特点是一条路走到黑;

2,代码模板实现

//DFS代码实现
void dfs(TreeNode root){
    if(root==null){
        return;
    }
    dfs(root.left);
    dfs(root.right);
}

//BFS代码实现
void bfs(TreeNode root){
    Queue<TreeNode> queue=new LinkedList<>();
    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 与 BFS 对比(这个图借鉴了这位大佬的力扣博客https://pic.leetcode-cn.com/fdcd3bd27f4008948084f6ec86b58535e71f66862bd89a34bd6fe4cc42d68e89.gif

 

典型例题

1.二叉树的深度

这个题很典型
//DFS解法
public int dfs(TreeNode root){
    if(root==null){
        return 0;
    }
    return Math.max(dfs(root.left),dfs(root.right))+1;
}


//BFS的解法
public int maxDepth(TreeNode root) {
    Queue<TreeNode> queue=new LinkedList<>();
    if(root==null) return 0;
    int deepth=0;
    queue.add(root);
    while(!queue.isEmpty()){
        int n=queue.size();
        for(int i=0;i<n;i++){
            TreeNode node=queue.poll();
            if(node.left!=null){
                queue.add(node.left);
            }
            if(node.right!=null){
                queue.add(node.right);
            }
        }
        deepth++;
    }
    return deepth;
}

2.层数最深叶子节点的和

这个题目用bfs简单一些,套用上面的模板

public int deepestLeavesSum(TreeNode root) {
    if(root==null) return 0;
    Queue<TreeNode> queue=new LinkedList<>();
    queue.add(root);
    int sum=0;
    while(!queue.isEmpty()){
        int n=queue.size();
        sum=0;
        for(int i=0;i<n;i++){
            TreeNode node=queue.poll();
            sum+=node.val;
            if(node.left!=null){
                queue.add(node.left);
            }
            if(node.right!=null){
                queue.add(node.right);
            }
        }
    }
    return sum;
}

3.祖父节点值为偶数的节点和

这个问题还是利用bfs的层序遍历,仍然可以套用模板,代码如下:

//bfs的解法,先判断当前的节点值是否为偶数,如果为偶数,判断其孙子节点是否存在,如果存在,则累加
public int sumEvenGrandparent(TreeNode root) {
        if(root==null) return 0;
        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(root);
        int sum=0;
        while(!queue.isEmpty()){
            TreeNode node=queue.poll();
            if(node.val%2==0){ 
                if(node.left!=null){
                    if(node.left.left!=null) sum+=node.left.left.val;
                    if(node.left.right!=null) sum+=node.left.right.val;
                }
                if(node.right!=null){
                    if(node.right.left!=null) sum+=node.right.left.val;
                    if(node.right.right!=null) sum+=node.right.right.val;
                }
            }
            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }
        return sum;
    }

当然这题也可以使用DFS的方法:先通过dfs找出所有满足题目要求的节点,然后再进行判断。具体同时维护三个节点(爷爷,父亲,当前节点),然后对爷爷进行判断,符合条件则把当前节点的值累加。然后继续维护(父亲,当前节点,当前节点的左右子节点)。这里进行了一个优化。其实我们在判断的时候没有用到爷爷节点和父节点,只是用到了他们的值,所以可以省去判断。在dfs的开始,我们用1代替祖父节点和父节点的值,进一步省去判断。

class Solution {
    int sum=0;
    public int sumEvenGrandparent(TreeNode root) {
        dfs(1,1,root);
        return sum;
    }
    private void dfs(int grandp_val,int p_val,TreeNode node){
        if(node==null) return;
        if(grandp_val%2==0) sum+=node.val;
        dfs(p_val,node.val,node.left);
        dfs(p_val,node.val,node.right);
    }
}

BFS的应用

一、层序遍历问题

1,leetcode102.二叉树的层序遍历

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

示例: 二叉树:[3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

由上面的分析可知,此题使用BFS,由于要将每层的数保存到列表里,则需要判断一下每层一共有多少个数字,代码如下:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> lists=new ArrayList<>();
        Queue<TreeNode> queue=new LinkedList<>();
        if(root==null) return new ArrayList<>();
        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);
                }
            }
            lists.add(list);
        }
        return lists;
    }
}

当然,这个题目如果非要用DFS也可以。因为DFS是一路向下遍历的,如果要实现每层节点值放在同一个列表里,就要引入一个level变量,在遍历的时候,将同一个level层的节点值放在同一个列表里,如果此时遍历的节点的level大于现在lists的size的话,就lists里新建一个list再存入。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> lists=new ArrayList<>();
        if(root==null) return lists;
        dfs(lists,root,0);
        return lists;    
    }
    private void dfs(List<List<Integer>> lists,TreeNode node,int level){
        if(lists.size()<=level){   //此处要想清楚
            lists.add(new ArrayList<>());
        }
        lists.get(level).add(node.val);
        if(node.left!=null){
            dfs(lists,node.left,level+1);
        }
        if(node.right!=null){
            dfs(lists,node.right,level+1);
        }
    }
}

二、最短路径问题

这里需要解释一下为什么最短路径问题适合用BFS,而不是DFS,dfs的算法实现就是遍历所有可能存在的路径。因此要想得到最短路径,毕竟要遍历所有的路径进行比较。而使用bfs进行遍历的话,如果某个节点灭有左节点或者右节点,那么它就是最短路径的终点,此时就可以得出结果,没有继续遍历的必要了。下面看一个例题

1.地图分析

你现在手里有一份大小为 N x N 的「地图」(网格) grid,上面的每个「区域」(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋区域,这个海洋区域到离它最近的陆地区域的距离是最大的。

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。

如果我们的地图上只有陆地或者海洋,请返回 -1。

示例 1:

输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。

示例2:

输入:[[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋区域 (2, 2) 和所有陆地区域之间的距离都达到最大,最大距离为 4。

解析:这个题目不是常见的二叉树,但是同样可以用层序遍历的方式,采用扩散的思想来解题(这个形容是看到了甜姨分享的题解才焕然大悟,之前做出来但是不知道怎么去形容)。具体做法是将所有陆地加入队列,然后同时向外扩散,没扩散一圈,数值加1,当地图上没有海洋的时候,此时的数值就是所求。具体代码如下:

public int maxDistance(int[] grid){
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    Queue<int[]> queue=new ArrayDeque<>();
    int con=grid.length,row=grid[0].length;
    //第一步把所有的陆地坐标都放入队列
    for(int i=0;i<con;i++){
        for(int j=0;j<row;j++){
            if(grid[i][j]==1){
                queue.offer(new int[]{i,j});
            }
        }
    }
    boolean hasOcean=false; //此处加它是因为全都是陆地没有海洋的情况
    int point=null;
    while(!queue.isEmpty()){
        point=queue.poll();
        int newx=point[0];
        int nexy=point[1];
        //这一步是把陆地的四周遍历一遍,直接再本来的数值上面加1
        for(int i=0;i<4;i++){
            int x=newx+dx[i];
            int y=newy+dx[i];
            //判断陆地四周坐标是否溢出和当前坐标是否是海洋
            if(x>=0 && x<con && y>=0 && y<row && grid[x][y]==0){
                hasOcean=true;
                grid[x][y]=grid[newx][newy]+1;
                queue.offer(new int[x][y]);
            }
        }
    }
    if(point==null || !hasOcean) return -1;
    return grid[point[0]][point[y]]-1;
}

2.最小高度树

对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,在所有可能的树中,具有最小高度的树被称为最小高度树。给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。

格式

该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。

你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,因此不会同时出现在 edges 里。

输入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]

    0
    |
    1
   / \
  2   3 
 输出: [1]

示例 2:

 输入: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
 0  1  2
  \ | /
    3
    |
    4
    |
    5 
输出: [3, 4]

思路:其实就是把所有的情况放进一个二维数组,并且用一个数组记录每个数出现的个数,接下来的操作就像是剥洋葱一样,从最外面开始剥(也就是最外面一层的数出现的个数为1),具体实现如下:

class Solution {
    private boolean[][] graph;
    private boolean[] visited;
    private int[] e;
    private Queue<Integer> queue;
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        graph=new boolean[n][n];
        visited=new boolean[n];
        e=new int[n];
        queue=new LinkedList<>();
        //将所有的数压入二位数组,并且记录每个数出现的个数
        for(int i=0;i<edges.length;i++){
            graph[edges[i][0]][edges[i][1]]=true;
            graph[edges[i][1]][edges[i][0]]=true;
            e[edges[i][0]]++;
            e[edges[i][1]]++;
        }
        while(n>2){  //此处判断大于2的原因是可能会存在一个或者两个节点都满足最小高度树,比如例二
            findOuter(); //将最外层的数压入队列
            while(!queue.isEmpty()){
                Integer v=queue.poll();
                e[v]--;
                n--;  //此处注意
                visited[v]=true;
                for(int i=0;i<graph[v].length;i++){
                    if(graph[v][i]){
                        e[i]--;
                        graph[v][i]=false;
                        graph[i][v]=false;
                    }
                }
            }
        }
        List<Integer> res=new ArrayList<>();
        for(int i=0;i<visited.length;i++){
            if(!visited[i])
                res.add(i);
        }
        return res;
    }
    public  void findOuter(){
        for(int i=0;i<e.length;i++){
            if(e[i]==1)
                queue.add(i);
        }
    }
}

上面分享了一些dfs和bfs的典型例题,最后分享了几个bfs的应用。

  • 15
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值