树--重建二叉树

前序+中序  中序+后序是可以构建出二叉树的

但是没有中序是不行的

 

概述

树是一种在实际编程中经常遇到的数据结构。它的逻辑很简单:除根节点之外每个节点只有一个父节点,根节点没有父节点:除叶节点之外所有节点都有一个或多个子节点,叶节点没有子节点。 父节点和子节点之间用指针链接。由于树的操作会涉及大量的指针,因此与树有关的面试题都不太容易。

 

面试的时候提到的树,大部分是二叉树。所谓二叉树是树的一种特殊结构,在二叉树中每个节点最多只能有两个子节点。在二叉树中最重要的操作莫过于遍历,

深度遍历

二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历。具体说明如下:

即按照某一顺序访问树中的所有节点。通常树有如下几种遍历方式。

 

• 前序遍历:先访问根节点,再访问左子节点,最后访问右子节点。图中的二叉树的前序遍历的顺序是10、6、4、8、14、12、16。

• 中序遍历:先访问左子节点,再访问根节点,最后访问右子节点。图2中的二叉树的中序遍历的顺序是4、6、8、10、12、14、16。

• 后序遍历:先访问左子节点,再访问右子节点,最后访问根节点。图中的二叉树的后序遍历的顺序是4、8、6、12、16、14、10。

 

二叉树有很多特例,二叉搜索树就是其中之一。在二叉搜索树中,左子节点总是小于或等于根节点,而右子节点总是大于或等于根节点。二叉树就是一棵二叉搜索树。我们可以平均在〇(log n )的时间内根据数值在二叉搜索树中找到一个节点。二叉搜索树的面试题有很多,如面试 题 36 “二叉搜索树与双向链表”、面试题68 “树中两个节点的最低公共祖先”。

 

二叉树的另外两个特例是堆和红黑树。堆分为最大堆和最小堆。在最大堆中根节点的值最大,在最小堆中根节点的值最小。有很多需要快速找到最大值或者最小值的问题都可以用堆来解决。红黑树是把树中的节点定义为红、黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。在 C++的 STL中,set、multiset、map、multiniap等数据结构都是基于红黑树实现的。与堆和红黑树相关的面试题,请参考面试题40 “最小的A个数”。

二、题目

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

1、思路

通常树有如下几种遍历方式:

  • 前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。
  • 中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。
  • 后序遍历:先访问左子结点,再访问右子结点,最后访问根结点。

本题为前序遍历和中序遍历,最少需要两种遍历方式,才能重建二叉树。

前序遍历序列中,第一个数字总是树的根结点的值。在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。剩下的我们可以递归来实现,具体如图:

先序遍历的顺序是根节点,左子树,右子树。中序遍历的顺序是左子树,根节点,右子树。

 

所以我们只需要根据先序遍历得到根节点,然后在中序遍历中找到根节点的位置,它的左边就是左子树的节点,右边就是右子树的节点。

 

生成左子树和右子树就可以递归的进行了。

 

比如上图的例子,我们来分析一下。

 

preorder = [1,2,4,7,3,5,6,8]

inorder = [4,7,2,1,5,3,8,6]

首先根据 preorder 找到根节点是 1

   

然后根据根节点将 inorder 分成左子树和右子树

左子树

inorder [4,7,2]

 

右子树

inorder [5,3,8,6]

 

把相应的前序遍历的数组也加进来

左子树

Preorder[2,4,7]

inorder [4,7,2]

 

右子树

Preorder[3,5,6,8]

inorder[5,3,8,6]

 

现在我们只需要构造左子树和右子树即可,成功把大问题化成了小问题

然后重复上边的步骤继续划分,直到 preorder 和 inorder 都为空,返回 null 即可

private static TreeNode buildTree(int[] num1, int[] num2) {
    if (num1 == null || num2 == null || num1.length != num2.length || num1.length < 1) {
        return null;
    }
   return helper(num1,num2,0,0,num2.length-1);
}
/**
 * @Author liuhaidong
 * @Description
 * @param preStart 先序开始的时候
 * @param inStart 中序的开始
 * @param inEnd   中序的结束
 * @Date 15:43 2019/10/10 0010
 */
private static TreeNode helper(int[] num1, int[] num2,int preStart,int inStart,int inEnd){
    if(inStart > inEnd){
        return null;
    }
    //终止条件
    int currentVal = num1[preStart];
    TreeNode current = new TreeNode(currentVal);

    //开始找中序中的根节点,然后分出左子树和右子树
    int inIndex = 0;
    //记录根节点
    for(int i =inStart;i<=inEnd;i++){
        if(num2[i] == currentVal){
            inIndex = i;
        }
    }
    //分出左子树和右子树,让你和进行的递归
    TreeNode left = helper(num1,num2,preStart+1,inStart,inIndex-1);
    TreeNode right = helper(num1,num2,preStart+inIndex-inStart+1,inIndex+1,inEnd);
    current.left = left;
    current.right = right;
    return current;
}

主函数

public static void main(String[] args) {
        System.out.println("请输入前序序列");
        Scanner sc1 = new Scanner(System.in);
        String str1 = sc1.nextLine().trim();
        String[] strs1 = str1.substring(1,str1.length()-1).split("\\,");
        int[] num1 = new int[strs1.length];
        for(int i =0;i < strs1.length;i++){
            num1[i] = Integer.parseInt(strs1[i]);
        }
        System.out.println("请输入中序序列");
        Scanner sc2 = new Scanner(System.in);
        String str2 = sc2.nextLine().trim();
        String[] strs2 = str2.substring(1,str2.length()-1).split("\\,");
        int[] num2 = new int[strs2.length];
        for(int i =0;i < strs2.length;i++){
            num2[i] = Integer.parseInt(strs2[i]);
        }
       // ArrayList<TreeNode> path = new ArrayList<>();
//        printPostOrder(buildTree(num1,num2),path);
        TreeNode current =buildTree(num1,num2);
        System.out.println(current);
//        System.out.print("[");
//        for(int i =0;i<path.size();i++){
//            System.out.print(path.get(i).val+",");
//        }
//        System.out.print("]");
    }