<think>我们已知前序遍历序列和中序遍历序列可以唯一确定一棵二叉树。前序遍历的顺序是:根节点->左子树->右子树;中序遍历的顺序是:左子树->根节点->右子树。思路:1.前序遍历的第一个元素一定是根节点。2.在中序遍历序列中找到这个根节点,则根节点左侧是左子树的中序遍历序列,右侧是右子树的中序遍历序列。3.根据左子树的长度,可以在前序遍历序列中确定左子树的前序遍历序列和右子树的前序遍历序列。4.递归地构建左子树和右子树。步骤:假设我们有两个数组:preorder:前序遍历序列inorder:中序遍历序列我们需要两个序列的当前范围,以避免每次都复制数组。因此,我们使用下标范围来表示当前子树对应的序列。函数原型:TNode*InPreToTree(vector<int>&preorder,intpreStart,intpreEnd,vector<int>&inorder,intinStart,intinEnd)但是,为了查找根节点在中序遍历中的位置,我们可以使用一个哈希表来快速查找(假设节点值互不相同)。具体步骤:1.如果当前范围无效(preStart>preEnd或inStart>inEnd),返回空指针。2.从前序遍历序列的preStart位置取出根节点的值。3.创建根节点,值为该值。4.在中序遍历序列中找到根节点的位置(设为inRoot),则左子树节点个数为:leftSize=inRoot-inStart。5.递归构建左子树:左子树的前序遍历范围:preStart+1到preStart+leftSize左子树的中序遍历范围:inStart到inRoot-16.递归构建右子树:右子树的前序遍历范围:preStart+leftSize+1到preEnd右子树的中序遍历范围:inRoot+1到inEnd注意:我们使用一个哈希表map来存储中序遍历的值和索引的对应关系,这样在递归过程中可以快速定位根节点。定义TNode结构体:structTNode{intdata;TNode*left;TNode*right;TNode(intx):data(x),left(nullptr),right(nullptr){}};使用new操作符来动态分配节点:TNode*root=newTNode(rootValue);我们首先构建一个辅助函数,它使用哈希表来避免重复查找。完整代码框架:#include<vector>#include<unordered_map>usingnamespacestd;structTNode{intdata;TNode*left;TNode*right;TNode(intx):data(x),left(nullptr),right(nullptr){}};classSolution{public:TNode*buildTree(vector<int>&preorder,vector<int>&inorder){//构建中序遍历的哈希映射:值->索引unordered_map<int,int>inMap;for(inti=0;i<inorder.size();i++){inMap[inorder[i]]=i;}returnInPreToTree(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1,inMap);}TNode*InPreToTree(vector<int>&preorder,intpreStart,intpreEnd,vector<int>&inorder,intinStart,intinEnd,unordered_map<int,int>&inMap){//递归终止条件if(preStart>preEnd||inStart>inEnd){returnnullptr;}//前序遍历的第一个节点是根节点introotVal=preorder[preStart];TNode*root=newTNode(rootVal);//在中序遍历中找到根节点的位置intinRoot=inMap[rootVal];//计算左子树中的节点数量intleftSize=inRoot-inStart;//递归构建左子树root->left=InPreToTree(preorder,preStart+1,preStart+leftSize,inorder,inStart,inRoot-1,inMap);//递归构建右子树root->right=InPreToTree(preorder,preStart+leftSize+1,preEnd,inorder,inRoot+1,inEnd,inMap);returnroot;}};注意:我们假设没有重复元素,否则这种方法需要调整。现在,我们详细解释:1.TNode结构体:包含一个整型数据成员,以及左右子树的指针。构造函数用于初始化节点。2.new操作符:在堆上动态分配内存,并调用构造函数初始化节点。3.函数逻辑:通过递归分割序列,不断构建子树,最后返回根节点。测试:需要确保输入的前序遍历和中序遍历序列是同一棵二叉树的遍历结果,且节点值唯一。示例:前序遍历:[3,9,20,15,7]中序遍历:[9,3,15,20,7]构建过程:根节点为3,在中序遍历中找到3的位置(索引1),则左子树中序遍历为[9](1个节点),右子树中序遍历为[15,20,7](3个节点)。左子树的前序遍历:从preStart+1开始,取1个节点,即[9];右子树的前序遍历:取后面3个节点[20,15,7]。然后递归构建左子树:左子树的前序遍历[9],中序遍历[9]->节点9。递归构建右子树:右子树的前序遍历[20,15,7],中序遍历[15,20,7]->根节点20,再继续分割。这样就能构建出整棵树。注意:递归终止条件是当范围无效时返回空指针。引用说明:上述递归构建二叉树的思路是分治法的典型应用[^1]。</think>### 使用前序遍历和中序遍历序列构造二叉树
#### 问题分析
给定二叉树的前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树。这是因为:
- 前序遍历:根节点 → 左子树 → 右子树
- 中序遍历:左子树 → 根节点 → 右子树
通过前序遍历序列确定根节点,再结合中序遍历序列划分左右子树,即可递归构建整棵树。
#### 数据结构定义
```cpp
struct TNode {
int data; // 节点数据
TNode* left; // 左子树指针
TNode* right; // 右子树指针
// 构造函数
TNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
```
- **`new`操作符**:动态分配内存空间创建新节点,例如 `new TNode(val)` 在堆内存创建节点并返回指针。
#### 递归函数实现
```cpp
#include <vector>
#include <unordered_map>
using namespace std;
TNode* InPreToTree(
vector<int>& preorder, int preStart, int preEnd, // 前序序列范围
vector<int>& inorder, int inStart, int inEnd, // 中序序列范围
unordered_map<int, int>& inMap // 中序值-索引映射
) {
// 终止条件:序列为空
if (preStart > preEnd || inStart > inEnd)
return nullptr;
// 1. 从前序序列获取根节点值
int rootVal = preorder[preStart];
TNode* root = new TNode(rootVal); // 创建根节点
// 2. 在中序序列中定位根节点位置
int inRoot = inMap[rootVal];
// 3. 计算左子树节点数量
int leftSubtreeSize = inRoot - inStart;
// 4. 递归构建左子树
root->left = InPreToTree(
preorder, preStart + 1, preStart + leftSubtreeSize, // 左子树前序范围
inorder, inStart, inRoot - 1, // 左子树中序范围
inMap
);
// 5. 递归构建右子树
root->right = InPreToTree(
preorder, preStart + leftSubtreeSize + 1, preEnd, // 右子树前序范围
inorder, inRoot + 1, inEnd, // 右子树中序范围
inMap
);
return root;
}
// 封装函数
TNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
// 构建中序值-索引映射表
unordered_map<int, int> inMap;
for (int i = 0; i < inorder.size(); i++) {
inMap[inorder[i]] = i;
}
return InPreToTree(
preorder, 0, preorder.size() - 1,
inorder, 0, inorder.size() - 1,
inMap
);
}
```
#### 算法步骤详解
1. **定位根节点**
前序序列首元素 `preorder[preStart]` 即为根节点值。
2. **划分左右子树**
- 在中序序列中找到根节点位置 `inRoot`
- 左子树节点数:$leftSubtreeSize = inRoot - inStart$
- 子树范围计算:
- **左子树前序**:$[preStart+1,\ preStart+leftSubtreeSize]$
- **右子树前序**:$[preStart+leftSubtreeSize+1,\ preEnd]$
- **左子树中序**:$[inStart,\ inRoot-1]$
- **右子树中序**:$[inRoot+1,\ inEnd]$
3. **递归构建**
对左右子树范围递归调用 `InPreToTree`,连接返回的子树指针。
4. **终止条件**
当序列范围无效时(`preStart > preEnd`),返回 `nullptr`。
#### 时间复杂度
- **时间复杂度**:$O(n)$
每个节点仅处理一次,哈希表实现 $O(1)$ 的索引查询。
- **空间复杂度**:$O(n)$
哈希表存储 $n$ 个节点的索引,递归栈深度为树的高度。
#### 示例验证
**输入**:
- 前序:`[3, 9, 20, 15, 7]`
- 中序:`[9, 3, 15, 20, 7]`
**构建过程**:
1. 根节点 = `3`(前序首位)
2. 中序分割:左子树 `[9]`,右子树 `[15, 20, 7]`
3. 左子树前序:`[9]` → 节点 `9`
4. 右子树前序:`[20, 15, 7]` → 递归构建右子树
最终树结构:
```
3
/ \
9 20
/ \
15 7
```
> 该算法通过分治策略高效构建二叉树,核心在于利用前序定位根节点、中序划分子树边界[^1]。
---
### 相关问题
1. **如何通过后序遍历和中序遍历序列构造二叉树?**
2. **当二叉树节点值重复时,上述算法是否仍然有效?为什么?**
3. **非递归方法如何实现由前序和中序序列构建二叉树?**
4. **二叉树序列化与反序列化中,遍历序列的选择有哪些优劣?**