树上的BFS(Tree Breadth First Search)

Pattern: Tree Breadth First Search,树上的BFS

介绍来自链接:https://www.zhihu.com/question/36738189/answer/908664455 作者:穷码农

​ 这种模式基于宽度优先搜索(Breadth First Search (BFS)),适用于需要遍历一颗树。借助于队列数据结构,从而能保证树的节点按照他们的层数打印出来。打印完当前层所有元素,才能执行到下一层。所有这种需要遍历树且需要一层一层遍历的问题,都能用这种模式高效解决。

​ 这种树上的BFS模式是通过把根节点加到队列中,然后不断遍历直到队列为空。每一次循环中,我们都会把队头结点拿出来(remove),然后对其进行必要的操作。在删除每个节点的同时,其孩子节点,都会被加到队列中。

识别树上的BFS模式:

  • 如果你被问到去遍历树,需要按层操作的方式(也称作层序遍历)

labuladong的算法小抄 上的算法框架,值得看看:

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; 		// 核心数据结构:队列
    Set<Node> visited; 	// 避免走回头路

    q.offer(start); 	// 将起点加入队列
    visited.add(start);
    int step = 0; 		// 记录扩散的步数

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}

队列 q 就不说了,BFS 的核心数据结构;cur.adj() 泛指 cur 相邻的节点,比如说二维数组中,cur 上下左右四面的位置就是相邻节点;visited 的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要 visited


可以先去了解一下宽度优先搜索算法,再做题。

队列的四组API

方式抛出异常有返回值,不抛出异常阻塞 等待超时等待
添加addoffer()put()offer(,)
移除removepoll()take()poll(,)
检测队首元素elementpeek
方法功能返回值
add增加一个元索如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove移除并返回队列头部的元素如果队列为空,则抛出一个NoSuchElementException异常
element返回队列头部的元素如果队列为空,则抛出一个NoSuchElementException异常
offer添加一个元素并返回true如果队列已满,则返回false
poll移除并返问队列头部的元素如果队列为空,则返回null
peek返回队列头部的元素如果队列为空,则返回null
put添加一个元素如果队列满,则阻塞
take移除并返回队列头部的元素如果队列为空,则阻塞

经典题目:

1、Binary Tree Level Order Traversal (easy)

102. 二叉树的层序遍历

描述:

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

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

	3
   / \
  9  20
    /  \
   15   7

​ 返回其层次遍历结果:

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

解题思路: 层次遍历和按照顺序首先要想到的是队列数据结构。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null)
            return res;

        Queue<TreeNode> queue = new LinkedList<>();     // 用 LinkedList 实现队列数据结构
        queue.offer(root);      // 根节点入列

        while (!queue.isEmpty()){       // 队列不为空则遍历
            List<Integer> levelList = new ArrayList<>();      // 每一层的结果存放
            int level = queue.size();       // 每层的节点数
            for (int i = 0; i < level; i++) {           // 保存下一层到队列中
                if (queue.peek().left != null) {
                    queue.offer(queue.peek().left);     // 将队头 左节点 入列
                }
                if (queue.peek().right != null) {
                    queue.offer(queue.peek().right);    // 将队头 右节点 入列
                }
                levelList.add(queue.poll().val);        // 节点出列, 保存节点的值
            }
            res.add(levelList);
        }
        return res;
    }
}

2、Reverse Level Order Traversal (easy)

70. 二叉树的层次遍历 II

107. 二叉树的层次遍历 II

描述:

​ 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

例如:

​ 给定二叉树 [3,9,20,null,null,15,7],

   3
  / \
  9  20
    /  \
   15   7

​ 返回其自底向上的层次遍历为:

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

**解题思路:**上一题中的结果反转,可以将下一层的结果保存到结果集的首部。从头插入使用 LinkedList 实现效率比较高,底层实现是双向链表。ArrayList 底层实现是 Object 数组,使用头插的话要挪动所有的元素,效率低。

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        LinkedList<List<Integer>> res = new LinkedList<>();
        if (root == null)
            return res;

        Queue<TreeNode> queue = new LinkedList<>();     // 用 LinkedList 实现队列数据结构
        queue.offer(root);      // 根节点入列

        while (!queue.isEmpty()){       // 队列不为空则遍历
            int level = queue.size();       // 每层的节点数
            List<Integer> levelList = new ArrayList<>(level);      // 每一层的结果存放

            for (int i = 0; i < level; i++) {           // 保存下一层到队列中
                if (queue.peek().left != null) {
                    queue.offer(queue.peek().left);     // 将队头 左节点 入列
                }
                if (queue.peek().right != null) {
                    queue.offer(queue.peek().right);    // 将队头 右节点 入列
                }
                levelList.add(queue.poll().val);        // 节点出列, 保存节点的值
            }
            res.addFirst(levelList);    // 下一层加入到结果的头部
        }
        return res;
    }
}

3、Zigzag Traversal (medium)

103. 二叉树的锯齿形层次遍历

描述:

​ 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

例如:

给定二叉树 [3,9,20,null,null,15,7],

	3
   / \
  9  20
    /  \
   15   7

返回锯齿形层次遍历如下:

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

解题思路: 记录遍历的层数,偶数层的结果记录使用尾插,奇数层的结果记录使用头插。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        LinkedList<List<Integer>> res = new LinkedList<>();
        if (root == null)
            return res;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int level = 0;      // 记录层数

        while (!queue.isEmpty()){
            LinkedList<Integer> list = new LinkedList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                if (queue.peek().left != null){
                    queue.offer(queue.peek().left);
                }

                if (queue.peek().right != null){
                    queue.offer(queue.peek().right);
                }

                if (level % 2 == 0){                // 偶数层顺序插入
                    list.add(queue.poll().val);
                }else {                             // 奇数层倒序插入
                    list.addFirst(queue.poll().val);
                }
            }
            level++;         // 层数 + 1
            res.add(list);
        }
        return res;
    }
}

4、Level Averages in a Binary Tree (easy)

637. 二叉树的层平均值

描述:

​ 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

示例 1:

输入:
    3
   / \
  9  20
    /  \
   15   7
输出:[3, 14.5, 11]
解释:
第 0 层的平均值是 3 ,  第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。

解题思路: 记录每一层节点总和 / 每一层节点数

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> res = new ArrayList<>();
        if (root == null)
            return res;

        Queue<TreeNode> queue = new LinkedList<>();     // 队列
        queue.offer(root);                              // 头结点入列

        while(!queue.isEmpty()){
            int size = queue.size();                    // 每层节点数
            double sum = 0;                             // 每层和
            for (int i = 0; i < size; i++) {
                if (queue.peek().left != null){
                    queue.offer(queue.peek().left);     // 下层左节点
                }
                if (queue.peek().right != null){
                    queue.offer(queue.peek().right);    // 下层右节点
                }

                sum += queue.poll().val;                // 计算总和
            }
            res.add(sum / size);                        // 保存平均值
        }
        return res;
    }
}

5、Minimum Depth of a Binary Tree (easy)

111. 二叉树的最小深度

描述:

​ 给定一个二叉树,找出其最小深度。

​ 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

​ **说明:**叶子节点是指没有子节点的节点。

示例 1:

img

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

示例 2:

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

解题思路:

1、 使用宽度优先搜索,处理每层的节点时,如果有节点既没有左节点和右节点时即达到了叶子节点,直接返回该层的深度。处理完一层的节点后深度+1

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null)
            return 0;

        Queue<TreeNode> queue = new LinkedList<>();     // 队列
        queue.offer(root);                              // 头结点入列

        int depth = 1;      // 记录深度
        while(!queue.isEmpty()){
            int size = queue.size();                    // 每层节点数
            double sum = 0;                             // 每层和
            for (int i = 0; i < size; i++) {            // 处理一层的节点
                if (queue.peek().left != null){
                    queue.offer(queue.peek().left);     // 下层左节点
                }
                if (queue.peek().right != null){
                    queue.offer(queue.peek().right);    // 下层右节点
                }
                
                // 该节点为叶子节点,直接返回深度
                if (queue.peek().left == null && queue.peek().right == null){
                    return depth;
                }
                
                // 该节点出列
                queue.poll();   
            }
            depth++;    // 一层处理完后,深度 +1
        }
        return depth;
    }
}

2、使用递归:深度优先搜索。

思路:

  • 叶子节点的定义是左孩子和右孩子都为 null 时叫做叶子节点
  • 当 root 节点左右孩子都为空时,返回 1
  • 当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
  • 当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值

在这里插入图片描述

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null)
            return 0;

        int left = minDepth(root.left);
        int right = minDepth(root.right);
        //1.如果左孩子和右孩子有为空的情况,说明left和right 必然有一个为 0。直接返回 left + right + 1
        //2.如果都不为空,返回较小深度+1
        return root.left == null || root.right == null ? left + right + 1 : Math.min(left, right) + 1;
    }
}

6、Level Order Successor (easy)

面试题 04.06. 后继者

448. 二叉查找树的中序后继

描述

​ 给定一个二叉查找树,以及一个节点,求该节点在中序遍历的后继,如果没有则返回null

​ 保证p是给定二叉树中的一个节点。(您可以直接通过内存地址找到p)

样例

样例 1:

输入: {1,#,2}, node with value 1
输出: 2
解释: 
  1
   \
    2
    
中序遍历结果:[1, 2]

样例 2:

输入: {2,1,3}, node with value 1
输出: 2
解释: 
    2
   / \
  1   3
  
中序遍历结果:[1, 2, 3]

样例 3:

输入: root = [5,3,6,2,4,null,null,1], p = 6
输出: null
      5
     / \
    3   6
   / \
  2   4
 /   
1
 
中序遍历结果:[1, 2, 3, 4, 5, 6]
1、先序遍历二叉树:

二叉树为空,则空操作。否则

​ (1)访问根节点

​ (2)先序遍历左子树

​ (3)先序遍历右子树

2、中序遍历二叉树:

二叉树为空,则空操作。否则

​ (1)中序遍历左子树

​ (2)访问根节点

​ (3)中序遍历右子树

3、后序遍历二叉树:

二叉树为空,则空操作。否则

​ (1)后序遍历左子树

​ (2)后序遍历右子树

​ (3)访问根节点

一棵二叉查找树(BST)定义为:
  • 节点的左子树中的值要严格小于该节点的值。
  • 节点的右子树中的值要严格大于该节点的值。
  • 左右子树也必须是二叉查找树。
  • 一个节点的树也是二叉查找树。

节点的祖先是从根节点到该节点所经分支上的所有节点。反之,以某个节点为根的子树中的任一结点都称为该节点的子孙

遍历二叉树是以一定的规则将二叉树中的节点排成一个线性的序列,得到二叉树中的节点的先序序列中序序列后序序列。这实质上是对一个非线性结构进行线性化操作,使得每个节点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。例如下图中序序列为 DBHEAFCIG , A的前驱为E,后继为F

在这里插入图片描述

中序遍历的后继:

  • 当前节点存在右子树:那么当前节点的后继为右子节点的子树中最左端的节点。例如B存在右子树,则其后继为右子节点E的最左端节点H
  • 当前节点不存在右子树:

淦!卡了好几天不理解,先跳过,以后有时间再回来解决吧。

7、Connect Level Order Siblings (medium)

429. N 叉树的层序遍历

描述:

​ 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

​ 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

示例:

示例 1:

输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]

示例 2:

输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]

解题思路: 宽度优先搜索+队列

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null)
            return res;

        Queue<Node> queue = new LinkedList<>();     // 队列
        queue.offer(root);                          // 头结点入列

        while (!queue.isEmpty()){
            List<Integer> levelList = new ArrayList<>();        // 每一层的结果保存
            int size = queue.size();                            // 每一层的节点个数
            for (int i = 0; i < size; i++) {                    // 处理一层的节点
                queue.addAll(queue.peek().children);            // 保存下一层的节点
                levelList.add(queue.poll().val);                // 保存完下一层节点,节点出列并保存其值
            }
            res.add(levelList);     // 保存每一层的结果
        }
        return res;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值