首页 > 编程语言 >算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树

算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树

时间:2023-06-14 11:35:25浏览次数:50  
标签:遍历 中序 节点 二叉树 序列 前序


对不同结构的二叉树进行前序,后序或中序遍历时,得到的结果可以是相同的,例如:

算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树_算法

上面三种结构的二叉树在进行前序遍历时,得到的序列都是:{3,4,5},但如果给定两种遍历序列,例如再加上中序遍历序列:{4,3,5}, 那么二叉树的结构就可以唯一确定了,满足这两种遍历序列的二叉树只能是中间那颗二叉树。

于是算法题目是,给定一颗二叉树的两种遍历序列,一种序列是中序遍历,一种序列的前序遍历,要求你根据这两种遍历序列,还原出其对应的二叉树。假设给定两个序列,中序遍历的节点序列为:{1,2,3,4,5,6,7,8,9,10},前序遍历的节点序列为:{6,4,2,1,3,5,9,7,8,10},要求你给出算法,根据这两个遍历序列,构造出下面这颗二叉树:

算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树_算法_02

给定了前序遍历,那么我们可以知道哪些节点是祖先节点,哪些节点是后代节点,例如根据上面的前序遍历序列,我们可以知道的是,节点6是所以节点的祖先节点,节点4,2是节点6的后代节点,然而我们无法确定的是,节点4,2都在节点6的右边还是都在左边,或者节点4在左边,节点2在右边。这个确定我们就必须要参照中序遍历的节点次序。

如何确定节点4是节点6的左孩子还是右孩子呢,我们需要参照下中序遍历序列,如果节点4在中序遍历序列中,出现在节点6的前面,那么节点4是节点6的左孩子,或者节点4在节点6的左子树,反之如果节点4在中序遍历序列中,出现在节点6的右边,那么节点4是节点6的右孩子,或者节点4属于节点6的右子树。

由此我们推出二叉树的构造算法如下:

1, 从前序遍历序列中逐个取出节点值,如果当前二叉树为空,那么就拿当前节点值构造一个二叉树节点作为树的根节点。

2,如果此时二叉树不为空,那么先从根节点开始,在中序遍历序列中查看当前节点处于根节点的左边,还是右边,如果处于左边,表明当前节点属于根节点的左子树,如果当前根节点的左孩子为空,那么就把当前节点当做根节点的左孩子,要不然进入根节点的左子树。如果当前节点在根节点的右边,那么如果根节点的右孩子为空,那么把当前节点作为根节点的右孩子,要不然进入根节点的右子树。

3,在中序遍历序列中,查找当前节点与当前子树节点的相互位置,如果当前节点处于但却子树节点的左边,那么当前节点属于当前子树节点的左子树,如果当前子树节点的左孩子为空,则把当前节点作为当前树节点的左孩子,要不然进入节点的左子树,继续重复步骤3,如果当前节点在中序遍历序列中处于树节点的右边,那么当前节点属于树节点的右子树,如果树节点的右子树为空,那么把当前节点作为树节点的右孩子,要不然进入树节点的右子树,然后重复步骤3.

当前序遍历序列中的每个节点都依照上面算法步骤遍历一遍后,所构成的二叉树就是我们需要的二叉树,以下是相应的代码实现:

import java.util.HashMap;


public class BTreeBuilder {
    private HashMap<Integer, Integer> nodeMap = new HashMap<Integer, Integer>();
    private TreeNode root = null;

    public BTreeBuilder(int[] inorder, int[] preorder) {
        for (int i = 0; i < inorder.length; i++) {
            nodeMap.put(inorder[i], i);
        }

        buildTree(preorder);
    }

    private void buildTree(int[] preorder) {
        if (root == null) {
            root = new TreeNode(preorder[0]);
        }

        for (int i = 1; i < preorder.length; i++) {
            int val = preorder[i];
            TreeNode current = root;
            while (true) {
                if (nodeMap.get(val) < nodeMap.get(current.val)) {
                    if (current.left != null) {
                        current = current.left;
                    } else {
                        current.left = new TreeNode(val);
                        break;
                    }
                } else {
                    if (current.right != null) {
                        current = current.right;
                    } else {
                        current.right = new TreeNode(val);
                        break;
                    }
                }
            }
        }
    }

    public TreeNode getTreeRoot() {
        return root;
    }
}

在代码开始时,先遍历中序序列,把每个节点在中序队列中的位置放入到map中,以便后面查找。buildTree对应的就是前面所说的算法步骤,它遍历前序节点队列中每一元素值,然后执行前面所说的算法三步骤,把当前节点值作为一个节点插入到二叉树的合适位置。

最后getTreeRoot返回构建好后的二叉树根节点。我们再看看程序入口处代码:

import java.util.ArrayList;


public class BinaryTree {
   public static void main(String[] s) {

       int[] inorder = new int[]{1,2,3,4,5,6,7,8,9,10};
       int[] preorder= new int[] {6,4,2,1,3,5,9,7,8,10};
       BTreeBuilder treeBuilder = new BTreeBuilder(inorder, preorder);
       TreeNode root = treeBuilder.getTreeRoot();

       PrintTreeList pt = new PrintTreeList(root);
       pt.printTree();
   }
}

首先程序构建了一个节点中序遍历序列inorder 和前序遍历序列preorder,然后通过类BTreeBuilder来构建对应二叉树,拿到二叉树后,通过类PrintTreeList将二叉树的节点一层一层的打印出来,该类的算法原理我们在以前的课程中有过详细介绍,程序运行后打印的结果为:

6 4 9 2 5 7 10 1 3 8

不难发现,这个层级打印出来的节点序列,跟我们上头的二叉树是一致的,由此可见,我们的算法实现是正确的。

算法需要遍历前序队列中的每个节点,此时复杂度是O(n), 每访问一个节点,我们都需要在当前已经建好的二叉树中进行相应的查找,二叉树查找的算法复杂度是h, 也就是树的高度,对于含有n个节点的二叉树,其高度为lg(n),因此总的算法复杂度为O(n*lg(n)).

在算法实现中,我们需要使用一个map来存储树的节点及位置,因此空间复杂度为O(n).

更详实的讲解和代码调试演示,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树_遍历_03


标签:遍历,中序,节点,二叉树,序列,前序
From: https://blog.51cto.com/u_16160261/6476528

相关文章

  • 【视频】ARIMA时间序列模型原理和R语言ARIMAX预测实现案例
    全文链接:http://tecdat.cn/?p=32773原文出处:拓端数据部落公众号分析师:FeierLiARIMA是可以拟合时间序列数据的模型,根据自身的过去值(即自身的滞后和滞后的预测误差)“解释”给定的时间序列,因此可以使用方程式预测未来价值。任何具有模式且不是随机白噪声的“非季节性"时间序列......
  • Redis入门 – Jedis存储Java对象 - (Java序列化为byte数组方式)
     Redis入门–Jedis存储Java对象-(Java序列化为byte数组方式) 在Jedis开发中,我们很多时候希望直接把一个对象放到Redis中,然后在需要的时候取出来。Redis的key和value都支持二进制安全的字符串,存储Java对象不是问题,下面我们看一下如何来实现。 1要存储的对象现在写一个很土的J......
  • Java反序列化Commons-Collection篇06-CC5链
    <1>环境分析jdk:jdk8u65CC:Commons-Collections3.2.1pom.xml添加<dependencies><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId>......
  • Java反序列化之Commons-Collection篇05-CC2链
    <1>环境分析jdk:jdk8u65CC:Commons-Collections4.0pom.xml添加<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.0</version></dependency&g......
  • 1218.最长定差子序列
    问题描述1218.最长定差子序列(Medium)给你一个整数数组arr和一个整数difference,请你找出并返回arr中最长等差子序列的长度,该子序列中相邻元素之间的差等于difference。子序列是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从arr派生出来的序......
  • 300.最长递增子序列
    问题描述300.最长递增子序列本题简写为LIS问题,与LCS问题(最长公共子序列)相对。解题思路动态规划关键在于,dp[i]表示什么含义便于解这道题,子序列不一定连续,所以为了便于求解,dp[i]应该表示为以nums[i-1]结尾的最长严格递增子序列的长度;递推关系为:if(nums[i-1]>nums[j-......
  • 序列化和反序列化_demo
    参考:一文搞懂序列化与反序列化-知乎(zhihu.com)一、jdk序列化和反序列化module结构: FactInfo.javapackagecom.hmb;importjava.io.Serial;importjava.io.Serializable;publicclassFactInfoimplementsSerializable{@Serialprivatestaticfinall......
  • python基础day23 os模块和序列化模块
    os模块(重要,多)os模块是与操作系统交互的一个接口('a/aa/aaa/aaaa/aaaaa')#递归创建文件夹os.removedirs('a/aa/aaa')#上推删除空文件夹os.mkdir('aaa')#当前文件所在位置创建一个新的文件夹或文件os.mkdir('a.txt')os.rmdir('aaa')#删除当前文件所在位置平级......
  • Python利用jsonpickle库把对象序列化为json
    python中经常要保存一些数据,json是一种理想的存储格式,纯文本的,也方便阅读,但有时使用起来不太方便,比如下面的例子:a=jsonData['A']b=jsonData['B']只能按字典方式引用,还不支持自动完成,不如python对象使用方便.如果定义python类,使用方便,但是保存为文件时......
  • os模块、序列化模块、pickle和json的区别
    os模块#os模块是与操作系统交互的一个接口1.文件相关的os.makedirs('dirname1/dirname2')#可生成多层递归目录os.removedirs('dirname1')#若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推os.mkdir('dirname')#生成单级目录;相当于shell中mkd......