树上的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

方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待
添加 add offer() put() offer(,)
移除 remove poll() take() poll(,)
检测队首元素 element peek
方法 功能 返回值
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:

树上的BFS(Tree Breadth First Search)_第1张图片

输入: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 节点左右孩子都不为空时,返回左右孩子较小深度的节点值

树上的BFS(Tree Breadth First Search)_第2张图片

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

树上的BFS(Tree Breadth First Search)_第3张图片

中序遍历的后继:

  • 当前节点存在右子树:那么当前节点的后继为右子节点的子树中最左端的节点。例如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 children;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, List _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;
    }
}

你可能感兴趣的:(算法,算法)