zoukankan      html  css  js  c++  java
  • 剑指Offer07 重建二叉树

    剑指 Offer 07. 重建二叉树

    前置概念:

    前序:访问根节点,先序遍历左子树,先序遍历右子树;

    中序:中序遍历左子树,访问根节点,中序遍历右子树;

    后序:后序遍历左子树,后序遍历右子树,访问根节点;

    图示

    image-20210615001425913

    前序:A- B-D-G-H-E-C-F

    中序:G-D-H-B-E-A-C-F

    后序:G-H-D-E-B-F-C-A

    怎么构建一棵树:

    通过一组(前序-中序 或 中序-后序)来确定唯一一棵树;

    以该题的测试样例举例:

    前序遍历 preorder = [3,9,20,15,7]
    中序遍历 inorder = [9,3,15,20,7]
    

    前序的第一个数是根节点,在pre数组中找到根节点(3),然后在中序遍历中找到3的位置,那么中序遍历中3左边的(9)就是根节点的左子树(左节点),(15、20、7)就是根节点的右子树(右节点).

    通过以上规律可得到一颗树,即:

    image-20210615002449771

    解题思路如下:

    重建二叉树的重构函数分为三个步骤

    • 首先要找到当前元素节点,即pre中的第一个,
    • 接着在中序遍历中找到它的左右子树(此处表现为对前序数组、中序数组的分割操作),以便构造他的左右孩子。
    • 最后再将左右子树在分别放到重构函数中

    比如此时,我们找到3的右子树(20、15、7),如下图,它实质上适合上图一样的,只不过此时是以3为根节点,其他操作是一样。

    image-20210615003609859

    前序:3-20-15-7

    中序:3-15-20-7

      此时重复操作:找到当前元素节点,即前序中的第一个,接着便可以在中序遍历中找到他的左右子树,以便构造他的左右孩子。发现他没有左子树,将左孩子放入构造函数中,如图所示:

    image-20210615004256871

    前序:20-15-7

    中序:15-20-7

    此时重复操作:找到当前元素节点,即前序中的第一个,接着便可以在中序遍历中找到他的左右子树,以便构造他的左右孩子。将左右孩子放入重构函数中,即

    image-20210615212444750

      直到分割成叶子节点,不存在左右子树,他无法再进行分割,故返回自己。叶子节点返回后,其父节点的左右子树分别有了指向,便返回,一步一步向上返回,最后会返回整个二叉树

    通过两段代码来学习该题目:

    主要是通过第一段代码来理解第二部分的思想。

    第一部分代码段:

    leetcode官方题解:详细解释就不说了,重点关注下:size_left_subtree变量(左子树中的节点数目);

    1. 通过map的key值可以找到中序中的根节点——————>构造哈希映射,帮助我们快速定位根节点
    class Solution {
        private Map<Integer, Integer> indexMap;
    
        public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
            if (preorder_left > preorder_right) {
                return null;
            }
    
            // 前序遍历中的第一个节点就是根节点
            int preorder_root = preorder_left;
            // 在中序遍历中定位根节点
            int inorder_root = indexMap.get(preorder[preorder_root]);
            
            // 先把根节点建立出来
            TreeNode root = new TreeNode(preorder[preorder_root]);
            // 得到左子树中的节点数目
            int size_left_subtree = inorder_root - inorder_left;
            // 递归地构造左子树,并连接到根节点
            // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
            root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
            // 递归地构造右子树,并连接到根节点
            // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
            root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
            return root;
        }
    
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            int n = preorder.length;
            // 构造哈希映射,帮助我们快速定位根节点
            indexMap = new HashMap<Integer, Integer>();
            for (int i = 0; i < n; i++) {
                indexMap.put(inorder[i], i);
            }
            return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
        }
    }
    

    第二部分代码段:

    class Solution {
        public TreeNode buildTree(int [] pre,int [] in) {
            TreeNode root=ConstructCore(pre,0,pre.length-1,in,0,in.length-1);
            return root;
        }
     
        public TreeNode ConstructCore(int[] pre,int startPre,int endPre,int[] in,int startIn,int endIn)
        {
            //前序起点下标>前序终点下标 || 中序的起点下标 > 中序终点下标
            if(startPre>endPre||startIn>endIn)
                return null;
            //通过起点下标找到前序中的根节点,并创建
            TreeNode node = new TreeNode(pre[startPre]);
            //中序遍历
            for(int i=startIn;i<=endIn;i++)
            {		//在中序中找到了根节点  此时i就是根节点的下标
                if(in[i]==pre[startPre])
                {
                 //ConstructCore(前序遍历,前序左子树起点下标,前序左子树终点下标,中序遍历,中序左子树起点下标,中序左子树终点下标)
                    node.left = ConstructCore(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
                    node.right =ConstructCore(pre,startPre+i-startIn+1,endPre,in,i+1,endIn);
                    break;
                }
            }
            return node;
     
        }
    }
    

    上述代码最难理解部分如下:

    for(int i=startIn;i<=endIn;i++)
    {		//在中序中找到了根节点  此时i就是根节点的下标
        if(in[i]==pre[startPre])
        {
            node.left = ConstructCore(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
            node.right =ConstructCore(pre,startPre+i-startIn+1,endPre,in,i+1,endIn);
            break;
        }
    }
    

    我们分析node.left以及node.right:

    node.left = ConstructCore(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);

    ConstructCore(前序遍历数组,前序左子树起点下标(除去根节点),前序左子树终点下标,中序遍历,中序左子树起点下标,中序左子树终点下标)

    详解: startPre+i-startIn

    “ i ” 是当前根节点的下标, i-startin --->表示在左子树中的节点数目,前序左子树

    startPre+i-startIn--->表示了前序数组中左子树的终止下标

    image-20210615232617184

    node.right =ConstructCore(pre,startPre+i-startIn+1,endPre,in,i+1,endIn);

    ConstructCore(前序遍历,前序右子树起点下标,前序右子树终点下标,中序遍历,中序右子树起点下标,中序右子树终点下标)

    i-startin --->表示在左子树中的节点数目,

    startPre+i-startIn--->前序左子树终点下标

    startPre+i-startIn+1 ---->前序右子树起点下标

    i-1 -->中序右子树起点下标。

    参考文献:

    代码引自:重建二叉树--->https://www.cnblogs.com/MrSaver/p/9205436.html

    LeetCode剑指offer官方题解

  • 相关阅读:
    第三周学习进度
    计算最低价格
    第二阶段冲刺5
    第二阶段冲刺4
    第十三周进度条
    第二阶段冲刺3
    寻找小水王
    第二阶段冲刺2
    第二阶段冲刺1
    构建之法阅读笔记
  • 原文地址:https://www.cnblogs.com/xbhog/p/14892362.html
Copyright © 2011-2022 走看看