zoukankan      html  css  js  c++  java
  • 二叉树的遍历框架总结

    前序遍历框架如下:

    void traverse(TreeNode root) {
        if (root == null) return;
    
        // 前序遍历的代码
    
        traverse(root.left);
        traverse(root.right);
    }
    

    后序遍历的代码框架:

    void traverse(TreeNode root) {
        traverse(root.left);
        traverse(root.right);
        /* 后序遍历代码的位置 */
        /* 在这里处理当前节点 */
    }
    

    如果当前节点要做的事情需要通过左右子树的计算结果推导出来,就要用到后序遍历

    二叉搜索子树的最大键值和这一题为例:

    想计算子树中 BST 的最大和,站在当前节点的视角,需要做什么呢

    1、我肯定得知道左右子树是不是合法的 BST,如果这俩儿子有一个不是 BST,以我为根的这棵树肯定不会是 BST,对吧。

    2、如果左右子树都是合法的 BST,我得瞅瞅左右子树加上自己还是不是合法的 BST 了。因为按照 BST 的定义,当前节点的值应该大于左子树的最大值,小于右子树的最小值,否则就破坏了 BST 的性质。

    3、因为题目要计算最大的节点之和,如果左右子树加上我自己还是一棵合法的 BST,也就是说以我为根的整棵树是一棵 BST,那我需要知道我们这棵 BST 的所有节点值之和是多少,方便和别的 BST 争个高下,对吧。

    根据以上三点,站在当前节点的视角,需要知道以下具体信息

    1、左右子树是否是 BST。

    2、左子树的最大值和右子树的最小值。

    3、左右子树的节点值之和。

    只有知道了这几个值,我们才能满足题目的要求,后面我们会想方设法计算这些值。

    我们需要的这些变量都是可以通过后序遍历得到的。

    你计算以 root 为根的二叉树的节点之和,是不是可以通过左右子树的和加上 root.val 计算出来?

    你计算以 root 为根的二叉树的最大值/最小值,是不是可以通过左右子树的最大值/最小值和 root.val 比较出来?

    你判断以 root 为根的二叉树是不是 BST,是不是得先判断左右子树是不是 BST?是不是还得看看左右子树的最大值和最小值?

    所以用后序遍历会很方便

    要尽可能避免递归函数中调用其他递归函数

    层级遍历二叉树的代码框架如下:

    void traverse(TreeNode root) {
        if (root == null) return;
        // 初始化队列,将 root 加入队列
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
    
        while (!q.isEmpty()) {
            TreeNode cur = q.poll();
    
            /* 层级遍历代码位置 */
            System.out.println(root.val);
            /*****************/
    
            if (cur.left != null) {
                q.offer(cur.left);
            }
    
            if (cur.right != null) {
                q.offer(cur.right);
            }
        }
    }
    

    经典题目

    二叉树的最近公共祖先

    给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

    遇到任何递归型的问题,无非就是灵魂三问

    1、这个函数是干嘛的

    情况 1,如果pq都在以root为根的树中,函数返回的即使pq的最近公共祖先节点。

    情况 2,那如果pq都不在以root为根的树中怎么办呢?函数理所当然地返回null呗。

    情况 3,那如果pq只有一个存在于root为根的树中呢?函数就会返回那个节点。

    2、这个函数参数中的变量是什么的是什么

    函数参数中的变量是root,因为根据框架,lowestCommonAncestor(root)会递归调用root.leftroot.right;至于pq,我们要求它俩的公共祖先,它俩肯定不会变化的。

    把「以root为根」转移成「以root的子节点为根」,不断缩小问题规模

    3、得到函数的递归结果,你应该干什么

    先想 base case,如果root为空,肯定得返回null。如果root本身就是p或者q,比如说root就是p节点吧,如果q存在于以root为根的树中,显然root就是最近公共祖先;即使q不存在于以root为根的树中,按照情况 3 的定义,也应该返回root节点。

    以上两种情况的 base case 就可以把框架代码填充一点了:

    TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 两种情况的 base case
        if (root == null) return null;
        if (root == p || root == q) return root;
    
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
    }
    

    用递归调用的结果leftright来搞点事情。根据刚才第一个问题中对函数的定义,我们继续分情况讨论:

    情况 1,如果pq都在以root为根的树中,那么leftright一定分别是pq(从 base case 看出来的)。

    情况 2,如果pq都不在以root为根的树中,直接返回null

    情况 3,如果pq只有一个存在于root为根的树中,函数返回该节点。

    leftright非空,分别是pq,可以说明root是它们的公共祖先,但能确定root就是「最近」公共祖先吗?

    这就是一个巧妙的地方了,因为这里是二叉树的后序遍历啊!前序遍历可以理解为是从上往下,而后序遍历是从下往上,就好比从pq出发往上走,第一次相交的节点就是这个root,所以这个root当然是最近公共祖先了

    public class LowestCommonAncestor {
    
        public class TreeNode {
          int val;
          TreeNode left;
          TreeNode right;
          TreeNode(int x) { val = x; }
        }
    
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            // base case
            if(root == null) {
                return null;
            }
            // 如果遍历到的根节点为p或者q,说明在父节点的子树下找到了该节点,所以返回该父节点
            if(p==root||q==root) {
                return root;
            }
            // 后序遍历,递归调用,left是在父节点的左子树中找到的p节点或者q节点或者空节点,right同理
            TreeNode left = lowestCommonAncestor(root.left,p,q);
            TreeNode right = lowestCommonAncestor(root.right,p,q);
            // 如果左子树和右子树都找到了p,q节点,说明父节点就是最近公共祖先,因为是后序遍历,所以能确保该父节点就是最近的祖先
            if(left!=null&&right!=null) {
                return root;
            }
            // 如果左子树和右子树都没有找到p,q节点,说明p,q节点和该父节点没关系,返回null,表示该父节点的子树下不包含p,q节点
            if(left==null&&right==null) {
                return null;
            }
            // 在上边条件都不满足的情况下,说明在该父节点下只找到了p或者q其中一个节点,返回这个找到的节点即可,
            // 如果是最外层的递归调用可能是因为另一个节点在这个节点的子树下,已经遍历找到了这个节点,就直接返回了没有再去找另一个节点,或者是两个节点都在左子树的某一个父节点下,所以右子树没有找到节点,这里当然返回这两个节点所在树的子调用里的父节点也就是现在返回的left了,右子树同理
            return left!=null?left:right;
        }
    }
    
  • 相关阅读:
    博客园
    hdu 2071 Max Num
    函数的可选参数
    JqueryUI的使用方法
    [转]淘宝网的设计流程
    hover!= mouseover+mouseout。但hover=mouseenter + mouseleave
    转:理解Progressive enhancement
    jQuery对象和DOM对象的区别
    JS字符串的slice和splice
    内家武功招数
  • 原文地址:https://www.cnblogs.com/RealGang/p/14889632.html
Copyright © 2011-2022 走看看