树(层序遍历):剑指 Offer 32 - I. 从上到下打印二叉树

发布时间 2023-03-31 11:04:02作者: ZDREAMER

题目描述:

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

 

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

3
/ \
9 20
/ \
15 7


返回:

[3,9,20,15,7]

 

提示:

  1. 节点总数 <= 1000


解题思路:
  •题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。
  •BFS 通常借助 队列 的先入先出特性来实现。

 

 

 

算法流程:
1.特例处理: 当树的根节点为空,则直接返回空列表 [] ;
2.初始化: 打印结果列表 res = [] ,包含根节点的队列 queue = [root] ;
3.BFS 循环: 当队列 queue 为空时跳出;
  出队: 队首元素出队,记为 node;
  打印: 将 node.val 添加至列表 tmp 尾部;
  添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;
4.返回值: 返回打印结果列表 res 即可。

 

 

 

复杂度分析:
  时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次。
  空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

 

class Solution {
    public int[] levelOrder(TreeNode root) {
        if (root == null) // 空树则返回空数组
            return new int[0];
        Queue<TreeNode> q = new LinkedList<> (); // 借助一个队列,通过 BFS 实现按层遍历二叉树
        ArrayList<Integer> tmp =new ArrayList<> (); // 申请一个动态数组 ArrayList 动态添加节点值
        
        q.offer(root); // 根结点先入队
        while (q.size() != 0) {
            TreeNode node = q.poll(); // 取出当前队首元素
            tmp.add(node.val); 
            if(node.left != null) q.offer(node.left); // 左子节点入队
            if(node.right != null) q.offer(node.right); // 右子节点入队
        }

        // 将 ArrayList 转为 int数组并返回
        int[] res = new int[tmp.size()];
        for (int i=0; i<res.length; i++) {
            res[i] = tmp.get(i);
        }
        return res;
    }
}

 

 

必要知识:

java队列Queue知识:

什么是队列?
队列是一种特殊的线性表,遵循先入先出、后入后出的基本原则,一般来说,它只允许在表的前端进行删除操作,而在表的后端进行插入操作,但是java的某些队列运行在任何地方插入删除;比如我们常用的 LinkedList 集合,它实现了Queue 接口,因此,我们可以理解为 LinkedList 就是一个队列;

 

java队列特性
队列主要分为阻塞和非阻塞,有界和无界、单向链表和双向链表之分;

阻塞和非阻塞
阻塞队列
  •入列(添加元素)时,如果元素数量超过队列总数,会进行等待(阻塞),待队列的中的元素出列后,元素数量未超过队列总数时,就会解除阻塞状态,进而可以继续入列;
  •出列(删除元素)时,如果队列为空的情况下,也会进行等待(阻塞),待队列有值的时候即会解除阻塞状态,进而继续出列;
  •阻塞队列的好处是可以防止队列容器溢出;只要满了就会进行阻塞等待;也就不存在溢出的情况;
  •只要是阻塞队列,都是线程安全的;

非阻塞队列
  •不管出列还是入列,都不会进行阻塞,
  •入列时,如果元素数量超过队列总数,则会抛出异常,
  •出列时,如果队列为空,则取出空值;

一般情况下,非阻塞式队列使用的比较少,一般都用阻塞式的对象比较多;阻塞和非阻塞队列在使用上的最大区别就是阻塞队列提供了以下2个方法:

出队阻塞方法 : take()
入队阻塞方法 : put()

 

有界和无界
  •有界:有界限,大小长度受限制
  •无界:无限大小,其实说是无限大小,其实是有界限的,只不过超过界限时就会进行扩容,就行ArrayList 一样,在内部动态扩容

单向链表和双向链表
  •单向链表 : 每个元素中除了元素本身之外,还存储一个指针,这个指针指向下一个元素;

 

  •双向链表 :除了元素本身之外,还有两个指针,一个指针指向前一个元素的地址,另一个指针指向后一个元素的地址;

 

 

队列常用方法
  add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
  remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
  element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
  offer 添加一个元素并返回true 如果队列已满,则返回false(常用)
  poll 移除并返问队列头部的元素 如果队列为空,则返回null(常用)
  peek 返回队列头部的元素 如果队列为空,则返回null(常用)
  put 添加一个元素 如果队列满,则阻塞
  take 移除并返回队列头部的元素 如果队列为空,则阻塞
drainTo(list) 一次性取出队列所有元素

知识点: remove、element、offer 、poll、peek 其实是属于Queue接口。