代码随想录算法训练营第十一天| 二叉树递归遍历、层序遍历

今日内容

  • 二叉树理论基础
  • 递归遍历
  • 层序遍历

二叉树理论基础

二叉树的类型

做题时常见的二叉树主要有 满二叉树完全二叉树 二叉搜索树

满二叉树只有度为0和2的节点,且度为0的节点全部在同一层:

深度为 k 的满二叉树总共有 2 ^ k - 1 个节点。

完全二叉树除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。如图所示:

二叉搜索树则是有序且带数值的。二叉搜索树有如下特点:

  1. 若存在左子树,则根节点的值大于左子树上所有节点的值。
  2. 若存在右子树,则根节点的值小于右子树上所有节点的值。
  3. 左右子树也都是二叉搜索树。

二叉搜索树如下图所示: 

在二叉搜索树中还有个平衡二叉搜索树,也被称为AVL(Adelson-Velsky and Landis)树。平衡二叉搜索树要么是空树,要么左右子树的高度绝对值不能大于 1,并且左右子树也是平衡二叉搜索树。如图所示:

二叉树的存储方式

二叉树有两种存储方式:链式存储顺序存储。前者用指针,后者用数组。

链式存储的示意图如下:

顺序存储则是由数组存储,如下图所示:

用顺序存储的话,若父节点的下标为 i ,那么它的左子树为 i * 2 + 1,右子树为 i * 2 + 2 

不过一般来说,链式存储更好理解,顺序存储只需要了解了解就好。

二叉树的遍历方式

二叉树有深度遍历方式和广度遍历方式。

深度遍历:碰到子树就进去,碰到叶子节点后返回。有前序/中序/后序遍历问题(递归法、迭代法)

广度遍历:一层一层遍历。有层序遍历问题(迭代法)

层序遍历很好理解,主要是前中后序遍历容易搞混。前中后序遍历中的“前中后”指的是根节点的顺序,比如前序遍历则说明按照“根左右”顺序遍历,中序遍历则是按照“左根右”顺序。

深度遍历一般用递归方式解决,而递归可以使用栈解决

广度遍历一般则使用队列解决,因为队列先进先出的特性有助于一层层地遍历。

二叉树的定义

和链表一样,力扣平台默认给出了定义,但是我们还是应该自己手搓一下:

class TreeNode{
    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(){}
    
    public TreeNode(int val, TreeNode left, TreeNode right){
        this.val = val;
        this.left = left;
        this.right = right;
    }

递归遍历

文章链接:代码随想录 (programmercarl.com)

递归,非常非常非常容易被绕进去,原因就在于写递归前没有按照有效的步骤进行,纯纯按照玄学,这样肯定不行。

一般写递归时,要注意如下三点:

  1. 确定递归函数的参数和返回的值
  2. 设置递归函数的停止条件,以防栈溢出
  3. 确定单层递归的逻辑

下面就以二叉树的前中后序遍历为例。

Leetcode. 144 二叉树的前序遍历

 题目链接:144. 二叉树的前序遍历 - 力扣(LeetCode)

前序遍历的次序为“根左右”,并且要求返回一个列表,列表中的元素以前序遍历次序排布。

  1. 先分析递归函数的参数和返回值。首先我们要操作的是二叉树的节点,所以肯定需要传入节点。再者,题目要求一个列表,所以也需要传入一个列表对其进行操作。因为列表是引用类型,所以不需要递归函数返回东西了。
  2. 再思考递归函数的停止条件。在这个函数中,只有当节点为空时,递归函数才会停下。所以停止条件就是当节点为空时。
  3. 最后确定单层处理逻辑。在进行遍历时,我们要先读取根节点的值,再去读取左右子树的值。这就是单层的处理逻辑。

根据上述的分析,我们就可以写出如下代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preOrder(root, result);
        return result;
    }
    
    public void preOrder(TreeNode root, List<Integer> list){ // 参数和返回值
        if (root == null){return;} // 停止条件
        // 单层逻辑
        list.add(root.val);
        preOrder(root.left, list);
        preOrder(root.right, list);
    }
}

以这种思路,中序和后序遍历无非就是在单层逻辑上进行微调就好。

Leetcode. 94 二叉树的中序遍历

题目链接:94. 二叉树的中序遍历 - 力扣(LeetCode)

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inOrder(root, result);
        return result;
    }

    public void inOrder(TreeNode root, List<Integer> list){
        if (root == null){return;}
        // 不同之处
        inOrder(root.left, list);
        list.add(root.val);
        inOrder(root.right, list);
    }
}

Leetcode. 145 二叉树的后序遍历

题目链接:145. 二叉树的后序遍历 - 力扣(LeetCode)

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postOrder(root, result);
        return result;
    }

    public void postOrder(TreeNode root, List<Integer> list){
        if (root == null){return;}
        postOrder(root.left, list);
        postOrder(root.right, list);
        list.add(root.val);
    }
}

层序遍历

文章链接:代码随想录 (programmercarl.com)

正如之前提到的,层序遍历可以使用队列实现。因为队列先入先出的特性,非常适合一层一层去遍历元素。其原理如下图所示:

根据上述图示,我们可以写出这样一套模板:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()){
            int len = queue.size();
            List<Integer> temp = new ArrayList<>(); 
            // 每次 for 循环都表示遍历一层 
            for (int i = 0; i < len; i++){
                TreeNode elem = queue.peek();
                temp.add(elem.val);
                queue.poll();
                if (elem.left != null){queue.offer(elem.left);}
                if (elem.right != null){queue.offer(elem.right);}
            }
            result.add(temp);
        }
        return result;
    }
}

 同样根据上面的思路,可以写出一个递归的模板:

class Solution {
    public static void level(TreeNode n, List<List<Integer>> list, int depth){
        if (n == null){return;}
        if (list.size() == depth){
            List<Integer> temp = new ArrayList<Integer>();
            list.add(temp);    
        }
        list.get(depth).add(n.val);
        level(n.left, list, depth + 1);
        level(n.right, list, depth + 1);
    }

    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        level(root, result, 0);
        return result;
    }
}

Leetcode. 102 二叉树的层序遍历

题目链接:102. 二叉树的层序遍历 - 力扣(LeetCode)

代码如下:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            //TreeNode n = queue.peek();
            int len = queue.size();
            List<Integer> temp = new ArrayList<>();  
            for (int i = 0; i < len; i++){
                TreeNode elem = queue.peek();
                temp.add(elem.val);
                queue.poll();
                if (elem.left != null){queue.offer(elem.left);}
                if (elem.right != null){queue.offer(elem.right);}
            }
            result.add(temp);
        }
        return result;
    }
}

Leetcode. 107 二叉树的层序遍历Ⅱ

题目链接:107. 二叉树的层序遍历 II - 力扣(LeetCode) 

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            List<Integer> temp = new LinkedList<>();
            int len = queue.size();
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                temp.add(n.val);
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
            result.add(temp);
        }
        // 加个反转
        Collections.reverse(result);
        return result;
    }
}

 Leetcode. 199 二叉树的右视图

题目链接:199. 二叉树的右视图 - 力扣(LeetCode)

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
    /*
        本题特殊之处在于取右视图
        那么基本设想就是找每一层的最后一个元素
    */
        List<Integer> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int len = queue.size();        
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                if (i == len - 1){result.add(n.val);}
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);} 
            }
        }
        return result;
    }
}

 Leetcode. 637 二叉树的层平均值

题目链接:637. 二叉树的层平均值 - 力扣(LeetCode)

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            long sum = 0;
            double ave = 0.0;
            int len = queue.size();
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                sum += n.val;
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
            ave = (double)sum / len;
            result.add(ave);
        }
        return result;
    }
}

 Leetcode. 429 N叉树的层序遍历

题目链接:429. N 叉树的层序遍历 - 力扣(LeetCode)

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            List<Integer> temp = new ArrayList<>();
            int len = queue.size();
            Node n;
            for (int i = 0; i < len; i++){
                n = queue.peek();
                queue.poll();
                temp.add(n.val);
                List<Node> nodeTemp = n.children;
                for (int j = 0; j < nodeTemp.size(); j++){
                    queue.offer(nodeTemp.get(j));
                }
            }
            result.add(temp);
        }
        return result;
    }
}

Leetcode. 515 在每个树行中找最大值

题目链接:515. 在每个树行中找最大值 - 力扣(LeetCode) 

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){return result;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int len = queue.size();
            int max = 0;
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                if (i == 0){
                    max = n.val;
                } else {
                    max = max > n.val ? max : n.val;
                }
                queue.poll();
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
            result.add(max);
        }
        return result;
    }
}

Leetcode. 116 填充每个节点的下一个右侧节点指针 

题目链接:116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) 

class Solution {
    public Node connect(Node root) {
        if (root == null){return root;}
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int len = queue.size();
            for (int i = 0; i < len; i++){
                Node n = queue.peek();
                queue.poll();
                if (i == len - 1){
                    n.next = null;
                } else {
                    n.next = queue.peek();
                }
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
        }
        return root;
    }
}

Leetcode. 117 填充每个节点的下一个右侧节点指针Ⅱ

题目链接:117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode) 

class Solution {
    public Node connect(Node root) {
        if (root == null){return root;}
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int len = queue.size();
            for (int i = 0; i < len; i++){
                Node n = queue.peek();
                queue.poll();
                if (i == len - 1){
                    n.next = null;
                } else {
                    n.next = queue.peek();
                }
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
        }
        return root;
    }
}

 Leetcode. 104 二叉树的最大深度

题目链接:104. 二叉树的最大深度 - 力扣(LeetCode)

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null){return 0;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        while (!queue.isEmpty()){
            int len = queue.size();
            depth += 1;
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }   
        }
        return depth;
    }
}

Leetcode. 111 二叉树的最小深度 

题目链接:111. 二叉树的最小深度 - 力扣(LeetCode) 

class Solution {
    public int minDepth(TreeNode root) {
        // 找最先出现叶节点的层
        if (root == null){return 0;}
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        while (!queue.isEmpty()){
            int len = queue.size();
            depth += 1;
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                if (n.left == null && n.right == null){
                    return depth;
                } else {
                    if (n.left != null){queue.offer(n.left);}
                    if (n.right != null){queue.offer(n.right);}
                }
            }
        }
        return depth;
    }
}

总结

从未一次做那么多题,就算有模板,套的也很累。

不过对二叉树的理解更加深刻了,其实二叉树之前一直是一知半解,经过了这次刷题,对递归遍历和层序遍历的理解更清晰了一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DonciSacer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值