<think>
我们已知二叉树的后序遍历(左子树 -> 右子树 -> 根节点)和中序遍历(左子树 -> 根节点 -> 右子树),要求前序遍历(根节点 -> 左子树 -> 右子树)。
核心思想:利用后序遍历确定根节点,然后在中序遍历中定位根节点,从而划分左右子树,递归处理。
步骤:
1. 后序遍历的最后一个元素是当前二叉树的根节点。
2. 在中序遍历中找到根节点的位置,则根节点左侧是左子树的中序遍历,右侧是右子树的中序遍历。
3. 根据中序遍历中左右子树的节点数量,可以在后序遍历中划分出左右子树的后序遍历(左子树的后序遍历在后序遍历数组中连续出现,紧接着是右子树,最后是根节点)。
4. 递归处理左子树和右子树。
具体算法(递归函数):
假设:
post: 后序遍历数组,postStart和postEnd分别是当前子树在后序遍历数组中的起始和结束下标。
in: 中序遍历数组,inStart和inEnd分别是当前子树在中序遍历数组中的起始和结束下标。
递归函数的功能:根据给定的后序和中序序列,重构二叉树并输出前序遍历序列(或者存储前序遍历序列)。
注意:我们并不需要真正构建二叉树节点,只需要输出前序遍历序列即可。
递归步骤:
1. 如果当前子树为空(inStart > inEnd 或 postStart > postEnd),则返回。
2. 当前根节点:post[postEnd]。
3. 输出根节点(或者存储,因为前序遍历先访问根)。
4. 在中序遍历数组中找到根节点的位置,记为index。则:
左子树的中序遍历:inStart 到 index-1
右子树的中序遍历:index+1 到 inEnd
5. 计算左子树节点个数:leftSize = index - inStart
6. 左子树的后序遍历:postStart 到 postStart+leftSize-1
右子树的后序遍历:postStart+leftSize 到 postEnd-1 (因为最后一个节点是根)
7. 递归处理左子树:传入左子树的后序和中序范围
8. 递归处理右子树:传入右子树的后序和后序范围
但是注意:在递归左右子树之前,我们已经输出了根节点(前序遍历的顺序:根->左->右),所以先输出根节点,然后递归左子树,再递归右子树。
然而,如果我们只需要得到前序遍历序列,那么按照上述顺序输出即可。
具体代码实现(Java):
假设中序遍历数组为inOrder,后序遍历数组为postOrder。
递归函数定义:
void buildTree(int[] inOrder, int[] postOrder, int inStart, int inEnd, int postStart, int postEnd)
调用方式:给定整个中序和后序数组,以及初始范围:
inStart=0, inEnd=inOrder.length-1
postStart=0, postEnd=postOrder.length-1
递归内部:
根节点值:int rootVal = postOrder[postEnd];
输出根节点:System.out.print(rootVal + " "); // 或者加入列表
在中序数组中找到rootVal的位置index(注意:假设节点值唯一)
左子树节点数:int leftSize = index - inStart;
右子树节点数:int rightSize = inEnd - index;
左子树:
中序范围:inStart 到 index-1
后序范围:postStart 到 postStart+leftSize-1
右子树:
中序范围:index+1 到 inEnd
后序范围:postStart+leftSize 到 postEnd-1
然后递归:
buildTree(inOrder, postOrder, inStart, index-1, postStart, postStart+leftSize-1); // 左子树
buildTree(inOrder, postOrder, index+1, inEnd, postStart+leftSize, postEnd-1); // 右子树
注意:递归调用前我们先输出了根节点,所以整个序列就是前序遍历。
但是,如果树为空(inStart>inEnd)则返回。
另外,在递归左右子树之前输出根节点,这样符合前序遍历顺序。
举例验证:
后序:4 5 2 6 7 3 1
中序:4 2 5 1 6 3 7
第一步:后序最后一个1是根节点。
中序中1的位置是3(下标从0开始),则:
左子树中序:4 2 5 -> 下标0~2
右子树中序:6 3 7 -> 下标4~6
左子树节点数=3,所以后序中左子树的后序序列:前3个(4,5,2)-> 对应下标0~2
右子树的后序序列:接着的3个(6,7,3)-> 下标3~5(注意后序最后一个1是根,所以右子树的后序是下标3到5)
然后递归:
左子树:中序[0:2] = [4,2,5];后序[0:2] = [4,5,2] -> 左子树的根是2(后序最后一个元素)
右子树:中序[4:6] = [6,3,7];后序[3:5] = [6,7,3] -> 右子树的根是3
输出顺序:先输出根1,然后递归左子树输出左子树的根2,再递归左子树的左子树(中序4,后序4)输出4,然后左子树的右子树(中序5,后序5)输出5,然后再递归右子树输出3,再递归右子树的左子树(6)输出6,右子树的右子树(7)输出7?不对,右子树的中序是6,3,7,根是3,3的左子树是6,右子树是7。
所以前序输出:1,2,4,5,3,6,7
但标准的前序应该是:1,2,4,5,3,6,7
因此,完整代码:
注意:需要处理中序中查找根节点位置,我们可以提前用HashMap存储中序遍历的值和索引,避免每次循环查找。
优化:使用HashMap记录中序遍历每个值对应的索引,这样查找根节点位置只需要O(1)时间。
代码实现(Java):
```java
import java.util.*;
public class TreeTraversal {
private Map<Integer, Integer> inOrderIndexMap = new HashMap<>();
public void buildTree(int[] inOrder, int[] postOrder) {
// 构建中序遍历的索引映射
for (int i = 0; i < inOrder.length; i++) {
inOrderIndexMap.put(inOrder[i], i);
}
buildTreeHelper(inOrder, postOrder, 0, inOrder.length - 1, 0, postOrder.length - 1);
}
private void buildTreeHelper(int[] inOrder, int[] postOrder, int inStart, int inEnd, int postStart, int postEnd) {
if (inStart > inEnd || postStart > postEnd) {
return;
}
// 根节点是后序遍历的最后一个元素
int rootVal = postOrder[postEnd];
// 输出根节点(前序遍历)
System.out.print(rootVal + " ");
// 在中序遍历中找到根节点的位置
int rootIndex = inOrderIndexMap.get(rootVal);
int leftSize = rootIndex - inStart; // 左子树节点个数
// 递归构建左子树
// 左子树的中序遍历区间: [inStart, rootIndex-1]
// 左子树的后序遍历区间: [postStart, postStart+leftSize-1]
buildTreeHelper(inOrder, postOrder, inStart, rootIndex - 1, postStart, postStart + leftSize - 1);
// 递归构建右子树
// 右子树的中序遍历区间: [rootIndex+1, inEnd]
// 右子树的后序遍历区间: [postStart+leftSize, postEnd-1]
buildTreeHelper(inOrder, postOrder, rootIndex + 1, inEnd, postStart + leftSize, postEnd - 1);
}
public static void main(String[] args) {
TreeTraversal solution = new TreeTraversal();
int[] inOrder = {4, 2, 5, 1, 6, 3, 7};
int[] postOrder = {4, 5, 2, 6, 7, 3, 1};
solution.buildTree(inOrder, postOrder); // 输出前序遍历: 1 2 4 5 3 6 7
}
}
```
运行结果:1 2 4 5 3 6 7
注意:这个递归函数直接打印前序遍历序列,如果需要返回列表,可以改为将节点值加入列表。
修改为返回列表的版本:
```java
public List<Integer> getPreOrder(int[] inOrder, int[] postOrder) {
// 构建中序遍历的索引映射
for (int i = 0; i < inOrder.length; i++) {
inOrderIndexMap.put(inOrder[i], i);
}
List<Integer> preOrderList = new ArrayList<>();
buildTreeHelper(inOrder, postOrder, 0, inOrder.length - 1, 0, postOrder.length - 1, preOrderList);
return preOrderList;
}
private void buildTreeHelper(int[] inOrder, int[] postOrder, int inStart, int inEnd, int postStart, int postEnd, List<Integer> preOrderList) {
if (inStart > inEnd || postStart > postEnd) {
return;
}
int rootVal = postOrder[postEnd];
preOrderList.add(rootVal);
int rootIndex = inOrderIndexMap.get(rootVal);
int leftSize = rootIndex - inStart;
buildTreeHelper(inOrder, postOrder, inStart, rootIndex - 1, postStart, postStart + leftSize - 1, preOrderList);
buildTreeHelper(inOrder, postOrder, rootIndex + 1, inEnd, postStart + leftSize, postEnd - 1, preOrderList);
}
```
这样调用getPreOrder方法即可返回前序遍历的链表。
总结:已知后序遍历和中序遍历求前序遍历,通过递归划分左右子树,并按照前序遍历顺序(根、左、右)将根节点加入结果,递归左右子树即可。
注意:该方法假设二叉树节点值唯一,且输入的后序和中序遍历序列是同一棵二叉树的合法序列。
引用:后序遍历的顺序是左子树->右子树->根节点,中序遍历的顺序是左子树->根节点->右子树[^1]。通过后序遍历的最后一个元素确定根节点,然后在中序遍历中定位根节点,从而划分左右子树[^2][^3]。
</think>
### 根据后序遍历和中序遍历求前序遍历的方法
#### 核心原理
后序遍历(左→右→根)和中序遍历(左→根→右)的组合可唯一确定二叉树结构:
1. **后序末元素为根节点**
2. **在中序中找到根节点位置**:
- 左侧为左子树的中序序列
- 右侧为右子树的中序序列
3. **递归处理子树**:
- 左子树节点数 = 中序根节点左侧元素数
- 右子树节点数 = 中序根节点右侧元素数
#### 算法步骤(Java实现)
```java
import java.util.*;
class Solution {
private Map<Integer, Integer> inOrderMap = new HashMap<>();
// 主函数:输入后序和中序,返回前序序列
public List<Integer> getPreOrder(int[] inOrder, int[] postOrder) {
// 构建中序遍历的索引映射(值->位置)
for (int i = 0; i < inOrder.length; i++) {
inOrderMap.put(inOrder[i], i);
}
List<Integer> preOrder = new ArrayList<>();
buildTree(inOrder, postOrder,
0, inOrder.length - 1,
0, postOrder.length - 1,
preOrder);
return preOrder;
}
private void buildTree(int[] in, int[] post,
int inStart, int inEnd,
int postStart, int postEnd,
List<Integer> preOrder) {
if (inStart > inEnd) return;
// 后序末元素是当前根节点
int rootVal = post[postEnd];
// 前序:先添加根节点
preOrder.add(rootVal);
// 定位根节点在中序的位置
int rootIdx = inOrderMap.get(rootVal);
int leftSize = rootIdx - inStart; // 左子树节点数
// 递归构建左子树 (前序顺序:根->左->右)
buildTree(in, post,
inStart, rootIdx - 1,
postStart, postStart + leftSize - 1,
preOrder);
// 递归构建右子树
buildTree(in, post,
rootIdx + 1, inEnd,
postStart + leftSize, postEnd - 1,
preOrder);
}
}
```
#### 执行过程示例
输入:
- 中序遍历 = `[9,3,15,20,7]`
- 后序遍历 = `[9,15,7,20,3]`
步骤:
1. 后序末元素 `3` 是根节点
2. 在中序定位 `3`:
- 左子树中序:`[9]`(1节点)
- 右子树中序:`[15,20,7]`(3节点)
3. 递归左子树:
- 后序:`[9]` → 根节点 `9`
4. 递归右子树:
- 后序末 `20` 为根节点
- 中序划分:左 `[15]`,右 `[7]`
5. 前序输出序列:`[3,9,20,15,7]`
时间复杂度:$O(n)$,空间复杂度:$O(n)$(哈希表存储中序索引)[^1][^2][^3]
#### 关键总结
| 操作 | 关键步骤 |
|----------------|--------------------------------------|
| 根节点定位 | 后序遍历的最后一个元素 |
| 子树划分 | 中序遍历中根节点分隔左右子树 |
| 子树节点计算 | 左子树节点数 = `rootIdx - inStart` |
| 递归边界 | `inStart > inEnd` 时终止 |
> 此方法适用于所有二叉树结构(包括非完全二叉树),且能保证重建结果的唯一性。