在计算机科学中,二叉树是一种基础且重要的数据结构,广泛应用于各种算法中。给定二叉树的遍历结果,重建原始二叉树是一类常见问题。本文将介绍如何根据二叉树的不同遍历序列(前序+中序、中序+后序、前序+后序)来重建二叉树。
基础知识
- 前序遍历(Pre-order Traversal):访问顺序为 根节点 → 左子树 → 右子树。
- 中序遍历(In-order Traversal):访问顺序为 左子树 → 根节点 → 右子树。
- 后序遍历(Post-order Traversal):访问顺序为 左子树 → 右子树 → 根节点。
重建二叉树
最近连续三天出现了重建二叉树的练习,题目主要是围绕这几种遍历中选取两种结果(数组形式输入)然后得出一颗完整二叉树(层序遍历)输出。(对应链接分别附于标题后)
1. 前序遍历 + 中序遍历
原理:前序遍历的第一个元素是根节点,中序遍历中根节点将树分为左右子树。利用这些信息,我们可以定位根节点,并递归地构建左右子树。
构建二叉树的原理基于先序遍历和中序遍历的特性。在先序遍历中,遍历的顺序是根节点→左子树→右子树,而在中序遍历中,遍历的顺序是左子树→根节点→右子树。利用这两个特性,我们可以确定二叉树的根节点以及哪些节点属于左子树和右子树。
步骤解析
-
确定根节点:
- 先序遍历的第一个元素总是树的根节点,因为先序遍历总是先访问根节点。
-
分割中序遍历:
- 在中序遍历中找到根节点后,根节点左侧的所有元素都属于左子树,根节点右侧的所有元素都属于右子树。这是因为中序遍历的顺序是先左子树,然后是根节点,最后是右子树。
-
递归构建子树:
- 一旦我们知道了哪些节点属于左子树,我们就可以从先序遍历中找到左子树的根节点(即左子树的第一个元素),并递归地构建左子树。
- 同理,我们也可以找到右子树的根节点,并递归地构建右子树。
-
重复上述过程:
- 对于每个子树,都重复上述过程,直到所有的节点都被包含在树中。
关键点
- 先序遍历确定根节点:先序遍历始终是从根节点开始,所以它可以帮助我们快速定位到每个子树的根节点。
- 中序遍历分割子树:中序遍历中,根节点的位置可以帮助我们明确哪些节点属于左子树,哪些属于右子树。
- 递归构建:通过递归方法,我们可以从顶向下构建整个二叉树,每次递归都会创建一个新的树节点,并将其连接到其父节点。
- 索引映射:为了提高查找效率,我们可以提前将中序遍历的值和它们的索引存储在一个映射中,这样就可以在 O(1) 的时间内找到任何值的索引。
代码实现:
java
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public class Solution {
private int preIndex = 0;
private Map<Integer, Integer> inMap = new HashMap<>();
public TreeNode buildTreeFromPreIn(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) inMap.put(inorder[i], i);
return construct(preorder, 0, inorder.length - 1);
}
private TreeNode construct(int[] preorder, int inStart, int inEnd) {
if (inStart > inEnd) return null;
TreeNode root = new TreeNode(preorder[preIndex++]);
int inIndex = inMap.get(root.val);
root.left = construct(preorder, inStart, inIndex - 1);
root.right = construct(preorder, inIndex + 1, inEnd);
return root;
}
}
2. 中序遍历 + 后序遍历
原理:后序遍历的最后一个元素是根节点,中序遍历中根节点将树分为左右子树。通过定位根节点,我们可以递归地构建左右子树。
代码实现:
java
public class Solution {
private int postIndex;
private Map<Integer, Integer> inMap = new HashMap<>();
public TreeNode buildTreeFromInPost(int[] inorder, int[] postorder) {
postIndex = postorder.length - 1;
for (int i = 0; i < inorder.length; i++) inMap.put(inorder[i], i);
return construct(inorder, postorder, 0, inorder.length - 1);
}
private TreeNode construct(int[] inorder, int[] postorder, int inStart, int inEnd) {
if (inStart > inEnd) return null;
TreeNode root = new TreeNode(postorder[postIndex--]);
int inIndex = inMap.get(root.val);
//顺序交换,先递归右子树
root.right = construct(inorder, postorder, inIndex + 1, inEnd);
root.left = construct(inorder, postorder, inStart, inIndex - 1);
return root;
}
}
3. 前序遍历 + 后序遍历
原理:利用前序遍历确定根节点,后序遍历确定左右子树的范围。然而,仅凭这两种遍历结果,二叉树可能无法被唯一确定。当我们使用先序遍历和后序遍历数组来重建二叉树时,我们利用了这样一个事实:先序遍历的顺序是"根-左-右",而后序遍历的顺序是"左-右-根"。这意味着先序遍历的第一个元素是树的根节点,而后序遍历的最后一个元素也是同一个根节点。使用这些信息,我们可以按照以下步骤进行递归:
代码实现:
java
public class Solution {
int preorderIndex =0;
Map<Integer, Integer> map = new HashMap<>();
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
for (int i = 0; i < postorder.length; i++){
map.put(postorder[i],i);
}
return arrayToTree(preorder,postorder,0,preorder.length-1);
}
public TreeNode arrayToTree(int[] preorder, int[] postorder,int left,int right) {
if (left > right || preorderIndex >= preorder.length) {
return null;
}
TreeNode root = new TreeNode(preorder[preorderIndex++]);
if (left == right || preorderIndex >= preorder.length) {
return root;
}
int index = map.get(preorder[preorderIndex]);
root.left = arrayToTree(preorder, postorder, left, index);
root.right = arrayToTree(preorder, postorder, index + 1, right - 1);
return root;
}
}
总结
重建二叉树是一个深入理解二叉树遍历和递归算法的好例子。每种遍历方法的组合提供了不同的信息,我们可以利用这些信息来还原原始的树结构。需要注意的是,不是所有遍历组合都能唯一确定一棵二叉树,特别是在没有附加条件(如二叉搜索树等)的情况下。正确地理解和应用这些原理对于解决相关的算法问题至关重要。