前言
本文主要介绍如何用先序数组和中序数组重建一棵树。
正文
我们都知道一棵二叉树的遍历方式有先序遍历、中序遍历、后续遍历,那么如果先给出一个树的先序遍历和中序遍历,能否还原出一棵二叉树呢?
当然是可以的,下面一起来分析下。
思路分析
以下面图中二叉树为例,先来看下它的先序遍历和中序遍历。
先序遍历: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即可。
代码实现
经过上面的分析,来看一下代码的实现。
首先定义一个节点,代码如下:
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;
}
总结
本文主要介绍如何用先序数组和中序数组重建一棵树,在实现的过程中使用了递归的思想,根据先序遍历的创建头结点,根据中序遍历确定边界,整体分析起来还是比较简单清晰的。