• LeetCode102-二叉树的层序遍历


    题目描述

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

    二叉树:[3,9,20,null,null,15,7],
        3
       / 
      9  20
         / 
        15  7
    返回其层次遍历结果:
    [
      [3],
      [9,20],
      [15,7]
    ]
    

    分析

    先考虑只需要返回一个层序遍历结果而不用考虑到底是第几层。如上面只返回[3,9,20,15,7]
    我们可以借助于一个队列Q:
    首先把根元素放到Q中,while Q不为空时,一个个取出队首,将其不为null的子节点加入队列,直到队列为空。
    用上面的例子,先把第一层3加入队列,然后3出队,加入子节点9,20,这是第二次;然后分别取出9,20,加入子节点15,7,这是第三次,取出15,7,他们没有子节点,没有元素入队,队为空,结束。
    这个实现是比较简单的:

        //层序 : 利用队列
        public void layerTraversal(TreeNode root) {
            Queue<TreeNode> q= new LinkedBlockingQueue<>();
            TreeNode tmp ;
            q.add(root);
            while (q.size()>0){
                tmp = q.poll();
                System.out.print(tmp.val+" ");
                if (tmp.left!=null)q.add(tmp.left);
                if (tmp.right!=null)q.add(tmp.right);
            }
        }
    

    实际上也可以利用数组,只不过稍微复杂点,原理一样。

        //层序 : 利用数组
        public void layerTraversal_iter_arr(TreeNode root){
            TreeNode []treeNodes= new TreeNode[100];//缺点:难以预测大小,要么浪费空间,要么不足报错
            int in=0,out=0;
            treeNodes[in++]=root;
            while (in>out){
                if (treeNodes[out]!=null){
                    System.out.print(treeNodes[out].val+" ");
                    treeNodes[in++]=treeNodes[out].left;
                    treeNodes[in++]=treeNodes[out].right;
                }
                out++;
            }
        }
    

    现在加需求了:需要考虑层次,即需要把不同层的分开。
    我们如何在原来的基础上进行扩展了。
    考虑前面使用队列的实现,一个队列存放了所有的元素,不能区分层次。如果使用两个队列存放,那么不同层不就可以交替使用,从而分开了吗?
    比如有两个队列Q1、Q2,按上面的例子:

    1. 把root 3放到Q1
    2. Q1中3出队变空,子节点9,20放到Q2
    3. Q2出队变空,9,20的子节点15,7放到Q1
    4. Q1元素全部出队,没有子节点,两个队列都空了,程序结束。

    那么现在就好实现了:

    辅助双队列

        public List<List<Integer>> levelOrder(TreeNode root) {
            List<List<Integer>> res = new LinkedList<>();//结构
    
            LinkedList<TreeNode> q1 = new LinkedList<>();//Q1
            LinkedList<TreeNode> q2 = new LinkedList<>();//Q2
            LinkedList<TreeNode> cur,empty;//引用,分别指向当前使用的队列和空队列
            TreeNode tmp;
            if (root!=null)q1.add(root); //先把root加入Q1
            while (!q1.isEmpty() || !q2.isEmpty()){//至少一个队列不为空
                List<Integer> level = new LinkedList<>();
                if (q1.isEmpty()){ //使cur指向正在使用的队列,empty指向即将用于存储的空队列
                    cur = q2;
                    empty = q1;
                }else{
                    cur = q1;
                    empty= q2;
                }
                while (!cur.isEmpty()){//当前队列不为空,取出所有元素,把元素的非null子节点放到empty队列
                    tmp = cur.pop();
                    level.add(tmp.val);
                    if (tmp.left!=null)empty.add(tmp.left);
                    if (tmp.right!=null)empty.add(tmp.right);
                }
                res.add(level);
            }
            return res;
        }
    

    性能:

    执行用时 :1 ms, 在所有 Java 提交中击败了91.30%的用户
    内存消耗 :39.8 MB, 在所有 Java 提交中击败了5.71%的用户

    空间开销有点大,可以考虑使用数组代替队列。

    看了下其他答案,上面的解法还可以优化。
    我使用的是两个队列供不同层次交替使用从而分层,但是还有一种方式————添加分界节点。

    辅助单队列+分界节点

    在上面的实现基础上,层之间添加一个分界节点。
    使用上面的例子说明下:
    使用X表示分界节点

    1. 3入队,X入队
    2. 3,X出队,子节点9,20入队,X入队
    3. 9,20,X出队,子节点15,7入队,X入队
    4. 15,7,X出队,没有元素入队,X也不用入队,代表程序结束 (即:X只有在队列不为空的情况下入队)

    实现:

        public List<List<Integer>> levelOrder(TreeNode root) {
            LinkedList<TreeNode> q = new LinkedList<>();//辅助队列
            TreeNode dummy = new TreeNode(Integer.MAX_VALUE);//分界节点
    
            List<List<Integer>> res = new LinkedList<>();//result
            List<Integer> level =new LinkedList<>();//层元素数组
            TreeNode tmp;//临时节点
    
            if (root==null)return res;
            q.add(root);
            q.add(dummy);//第一层和第二层之间的分界节点
            while (!q.isEmpty()){
                tmp = q.pop();
                if (tmp==dummy){//遇到分界节点,表示已经遍历完一层了
                    res.add(level);
                    level = new LinkedList<>();//下一层的存放数组
                    if(!q.isEmpty()) q.add(dummy);//dummy只有在队列不为空的情况下入队,保证程序正常退出
                }else{//非分界节点
                    level.add(tmp.val);
                    if (tmp.left!=null)q.add(tmp.left);
                    if (tmp.right!=null)q.add(tmp.right);
                }
            }
            return res;
        }
    

    性能其实和上面差不多,空间其实也没省多少:元素都会放到队列中,双队列多一个队列的空间,单队列多一个分界节点。
    两种思维而已。

    递归实现

    前面两种层序遍历都是迭代实现,其实也可以递归实现
    递归实现的话由于不是一层一层的遍历的,那么怎么控制层数???
    我们可以添加一个参数level,代表当前的层数,这样就可以把元素添加到对应的层中的list中了,不管你是怎么递归,只要把元素遍历完就好了。
    实现:

        private List<List<Integer>> res = new LinkedList<>();//result
        public  List<List<Integer>> levelOrder_recur(TreeNode root){
            if (root==null)return res;
            helper(root,0);
            return res;
        }
        private void helper(TreeNode node,int level){
            if (res.size()==level)res.add(new LinkedList<>());//添加某层的List
            res.get(level).add(node.val);//获取level层,并添加元素
            if (node.left!=null)helper(node.left,level+1);//递归下去
            if (node.right!=null)helper(node.right,level+1);
        }
    

    递归实现代码量最少,也很容易理解。

    总结

    总共写了三种方法,层序和递归,如果要归一个类的话,就是BFS和DFS

    1. BFS,双队列
    2. BFS,单队列+分界节点
    3. DFS,使用level参数记录层次

    三种方法时间复杂度、空间复杂度都是O(N)

  • 相关阅读:
    Go 实现一个简单的TCP服务端
    将博客搬至CSDN
    My solution for Git Client Error: Permission denied (publickey)
    The Key to final data
    Design Pattern
    Difference between TCP and UDP
    Oracle SQL自带函数整理
    Java JDBC Batch
    Javascript Date 判断输入日期是否正确
    Javascript 数字保留2位小数
  • 原文地址:https://www.cnblogs.com/XT-xutao/p/12883085.html
Copyright © 2020-2023  润新知