美文网首页程序员java程序员
【剑指offer-03】重建二叉树

【剑指offer-03】重建二叉树

作者: d8a905c8d814 | 来源:发表于2018-08-26 00:44 被阅读1次

    问题描述

    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

    解题思路

    /**
     * Definition for binary tree
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    
    
    public class Solution {
        
        /**
        * 前提:树的每个节点的值不重复
        * 思路:
        * 利用递归的思想,为什么想到递归呢?
        * 
        * 前序数组的第一个是根节点,记为i,在中序数组中找到它,
        * 则中序数组中i的左边明显是根节点的“左子树的中序遍历”,长度记为r,
        * 
        * 而我们也可以找到“左子树的前序遍历”,
        * 这需要在前序数组中根节点右边开始算的r个元素为左子树的前序遍历
        * 
        * 接着,我们还可以寻找“右子树的中序遍历”,很明显,中序数组中i的右边就是
        * 剩下,我们寻找“右子树的前序遍历”,在前序数组中找,中序数组中i的右边的长度记为m,
        * 则前序数组中i的倒数m个元素就是我们要找的“右子树前序遍历”
        * 
        * 看完这些,你应该大概能理解,为什么用递归了,
        * 因为我们每次都是能很明确的从前序数组中找到树的根节点,
        * 根节点需要left指向左子树根,right需要指向右子树根,所以可以通过递归不断求根,直到没有子树,就构建完一棵二叉树。
        */
        
        public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
            
           return reConstructBinaryTreeInterval(pre, 0, pre.length - 1, in, 0, in.length -1);
        }
        
        /**
        * 参数说明: 
        * pre 原本前序数组
        * preI 树的前序遍历在前序数组中的起始下标
        * preJ 树的前序遍历在前序数组中的结束下标
        * in 原本的中序数组
        * inI 树的中序遍历在中序数组的起始下标
        * inJ 树的中序遍历在中序数组的结束下标
        */
        private TreeNode reConstructBinaryTreeInterval(int[] pre, int preI, int preJ, 
                 int[] in, int inI, int inJ) {
            // 结束条件,也就是没有子树的情况,返回null
            if(preI > preJ || inI > inJ) {
                return null;
            }
            
            // 构建根节点
            TreeNode head = new TreeNode(pre[preI]);
            
            // 在中序数组中找到根节点的位置
            int position = -1;
            for(int i = inI; i <= inJ; i++) {
                if(pre[preI] == in[i]) {
                    position = i;
                    break;
                }
            }
                
            // 计算出左子树有多少个节点,
            int r = position - inI;
            
            
            //递归构建左子树,需要在前序数组中找到左子树的前序序列范围--> preI + 1开始 到preI + r
            //和中序数组中找到左子树的中序遍历范围--> inI开始到position -1
            head.left = reConstructBinaryTreeInterval(pre, preI + 1, preI + r, in, inI, position - 1);
            
            //递归构建右子树,需要在前序数组中找到右子树的前序序列范围--> preI + r + 1开始,到preJ
            // 和中序数组中找到右子树的中序遍历范围--> position + 1 到 inJ
            head.right = reConstructBinaryTreeInterval(pre,preI + r + 1, preJ, in, position + 1, inJ);
            
            return head;
        }
    }
    

    根据上面利用前序数组和中序数组重建二叉树的思路,可以总结出根据后序-中序数组重建的过程,以及前序-后序数组重建过程。

    后序-中序 重建

    后序数组的特点是根节点就是数组的最后一个元素。

    我们只要得到下面这些信息,就好办了

    • 得出左子树的后序序列在后序数组的下标范围
    • 得出左子树的中序序列在中序数组的下标范围
    • 得出右子树的后序序列在后序数组的下标范围
    • 得出右子树的中序序列在中序数组的下标范围

    1. 得出左子树的中序序列在中序数组的下标范围

    中序序列数组中根节点左边的就是是左子树的中序遍历序列。同时可以得出左子树的长度。

    2. 得出左子树的后序序列在后序数组的下标范围

    后序数组中前N个元素就是左子树的后序序列,而上面已经可以求得这个N,也就可以算出下标范围了。

    3. 得出右子树的中序序列在中序数组中的下标范围

    中序数组中根节点的右边就是右子树的中序遍历序列, 因而下标范围可求。

    4. 得出右子树的后序序列在后序数组中的下标范围

    后序数组中前N个元素是左子树的后序序列,而剩下下标为N开始的到后序数组末尾不包括末尾,就是要求的下标范围。

    对比上面的代码,你很快就能写出中序-后序的构建过程。

    前序-后序 重建

    前序-后序重建其实并不一定能重建一棵二叉树,有时候前序和后序序列并不能唯一确定一棵二叉树。

    比如:头节点 为1, 左孩子为2,无右孩子,这时前序遍历:【1,2】;后序遍历:【2,1】。另一种情况,头节点为1,右孩子为2,无左孩子,这时前序和后序也是【1,2】和【2,1】。

    因此,仅根据前序和后序序列是无法重建一棵二叉树的。

    相关文章

      网友评论

        本文标题:【剑指offer-03】重建二叉树

        本文链接:https://www.haomeiwen.com/subject/ykuniftx.html