如何用先序数组和中序数组重建一棵树?

277 阅读4分钟

前言

本文主要介绍如何用先序数组和中序数组重建一棵树。

正文

我们都知道一棵二叉树的遍历方式有先序遍历、中序遍历、后续遍历,那么如果先给出一个树的先序遍历和中序遍历,能否还原出一棵二叉树呢?

当然是可以的,下面一起来分析下。

思路分析

以下面图中二叉树为例,先来看下它的先序遍历和中序遍历。

先序遍历:1->2->4->5->3->6->7

中序遍历:4->2->5->1->6->3->7

头结点创建

二叉树的头结点是哪一个呢?是先序遍历的第一个节点,所以先把头结点确定下来,头结点为1

左子树创建

头结点的左节点如何确定呢?看图中的示例,左子树中的节点为2,4,5,这3个节点刚好为先序遍历的头结点1的后3个数,也是中序遍历的头结点1的前3个数。

所以2,4,5这三个节点,根据先序遍历和中序遍历,可以确定这三个数的头结点为2

右子树创建

头结点的右节点如何确定呢?看图中的示例,右子树中的节点为3,6,7,这3个节点刚好为先序遍历的最后3个数,也是中序遍历的头结点1的后3个数。

所以3,6,7这三个节点,根据先序遍历和中序遍历,可以确定这三个数的头结点为3

递归创建

以此类推,我们可以设计一个函数,将先序遍历的数组和中序遍历的数组作为参数传进来,采用递归的方式,可以确定每一个节点的左节点和右节点。

递归时,刚开始传递的是整个数组的长度,后续需要传递下一递归时先序遍历和后续遍历的范围,当递归时,先序遍历或后续遍历都只有一个数的时候,就是该次遍历最后一个节点,直接创建返回即可。

递归的范围

如何知道递归的范围呢?因为中序遍历的头结点在中间,所以根据先序遍历确定头结点后,从中序遍历寻找头结点,中序遍历头结点左、右就是下次遍历的左子树和右子树。

边界确定

递归范围的边界如何确定呢?以左子树为例,假如先序遍历的整体范围为[X,Y],中序遍历整体范围为[M,N],中序遍历的的头结点下标为T,则,下次递归时,先序遍历的范围为[X+1,X+T-M],中序遍历的范围为[M,T-1]

有了左子树的范围,右子树的范围就好确定了,先序遍历的右子树范围为[X+T-M+1,Y],中序遍历右子树的范围为[T+1,N]

注意

需要注意,如果一个二叉树,只有左子树或只有右子树,需要特殊判断下,需要判断的地方就是左右子树的下标范围,不能让左下标大于右下标,如果出现这种情况的话,说明没有该节点,直接返回null即可。

image.png

代码实现

经过上面的分析,来看一下代码的实现。

首先定义一个节点,代码如下:

public class TreeNode {
   public int val;
   public TreeNode left;
   public TreeNode right;

   public TreeNode(int val) {
      this.val = val;
   }
}

创建一个函数,接收先序遍历和中序遍历的数组,如下:

public TreeNode buildTree(int[] pre, int[] in) {
   if (pre == null || in == null || pre.length != in.length) {
      return null;
   }
   return func(pre, 0, pre.length - 1, in, 0, in.length - 1);
}

接下来就是递归函数的实现了:

public TreeNode func(int[] pre, int X, int Y, int[] in, int M, int N) {
    if (X > Y) {
            return null;
    }
    TreeNode head = new TreeNode(pre[X]);
    if (X == Y) {
            return head;
    }
    int T = M;
    while (in[T] != pre[X]) {
            T++;
    }
    head.left = func(pre, X + 1, X + T - M, in, M, T - 1);
    head.right = func(pre, X + T - M + 1, Y, in, T + 1, N);
    return head;
}

总结

本文主要介绍如何用先序数组和中序数组重建一棵树,在实现的过程中使用了递归的思想,根据先序遍历的创建头结点,根据中序遍历确定边界,整体分析起来还是比较简单清晰的。