<think>好的,我现在需要解决用户的问题:如何根据给定的前序遍历和中序遍历序列构建二叉树,并且使用C++实现。让我仔细理清思路,确保自己正确理解每一步,避免犯错。
首先,用户提供的前序遍历是3、9、20、15、7,中序遍历是9、3、15、20、7。我需要回忆二叉树遍历的基本知识。前序遍历的顺序是根节点-左子树-右子树,而中序遍历是左子树-根节点-右子树。因此,前序遍历的第一个元素就是整个树的根节点,这里是3。接下来,我需要在中序遍历中找到这个根节点,根节点左边的部分是左子树的中序遍历,右边是右子树的中序遍历。
接下来,我需要确定左子树和右子树的大小。例如,中序遍历中根节点3左边的元素是9,说明左子树只有一个节点9。右边的部分是15、20、7,这是右子树的中序遍历。对应的前序遍历中,根节点之后的部分应该先是左子树的前序遍历,然后是右子树的前序遍历。由于左子树只有一个节点,所以前序遍历中根3之后的下一个元素9就是左子树的根,剩下的20、15、7是右子树的前序遍历部分。
现在,我需要递归地构建左右子树。对于左子树,前序遍历只剩下9,中序遍历也是9,所以左子树只有一个节点9。右子树的前序遍历是20、15、7,中序遍历是15、20、7。这时候,右子树的根节点是前序遍历的第一个元素20。然后在中序遍历中找到20的位置,左边是15,右边是7。所以右子树的左子树是15,右子树是7。对应的前序遍历中,20后面跟着的15和7应该分为左子树和右子树的前序部分,这里可能需要进一步分析。
不过,这里可能存在一个问题:右子树的前序遍历是20、15、7,而中序遍历是15、20、7。所以,根节点是20,左子树的中序是15,右子树的中序是7。那么前序遍历中,根20之后的15应该是左子树的根,剩下的7是右子树的根。这样,右子树的结构应该是根20,左子节点15,右子节点7。
接下来,我需要考虑如何将这些步骤转化为C++代码。通常,构建二叉树需要递归的方法。递归函数需要接收当前子树的前序遍历和中序遍历的区间,然后根据根节点划分左右子树,并递归构建左右子树。
可能的步骤:
1. 确定根节点的值,即前序遍历的第一个元素。
2. 在中序遍历中找到根节点的位置,分割左右子树的中序遍历序列。
3. 根据左子树的大小,分割前序遍历序列为左子树的前序和右子树的前序。
4. 递归构建左子树和右子树。
需要注意的是,分割时数组的索引是否正确。例如,如果中序遍历中根的位置是index,那么左子树的大小是index - inStart。前序遍历中左子树的部分是从preStart+1到preStart + leftSize,右子树则是剩下的部分。
现在,我需要考虑代码的具体实现。首先定义二叉树的结构,每个节点有值、左子节点和右子节点。然后编写递归函数,参数包括前序遍历的起始和结束索引,中序遍历的起始和结束索引。使用哈希表存储中序遍历的值到索引的映射,以便快速查找根节点位置。
可能的边界情况包括当中序遍历的起始大于结束索引时,返回nullptr。例如,当左子树不存在时,左子树的递归调用应该终止。
在具体实现中,需要注意数组的索引是否正确。例如,前序遍历的起始位置每次取第一个元素作为根,然后左子树的前序遍历范围是preStart+1到preStart + leftSize,右子树的前序遍历范围是preStart + leftSize +1到preEnd。中序遍历的左子树范围是inStart到rootIndex-1,右子树是rootIndex+1到inEnd。
现在,我需要验证这个思路是否正确。例如,给出的例子:
前序:3,9,20,15,7
中序:9,3,15,20,7
根是3,在中序中的位置是索引1。左子树的中序是9(索引0到0),左子树的前序应该是前序中根之后的一个元素,即9。右子树的中序是15,20,7(索引2到4)。对应的右子树的前序是20,15,7。此时,右子树的根是20,在中序中的位置是索引2。左子树的中序是15(索引2到1?这里可能有问题,需要仔细检查)。
哦,这里似乎有错误。中序中根是3的位置是索引1,所以右子树的中序应该从索引2到4,即15,20,7。那么右子树的根是前序中的下一个元素,即20。在中序的右子树部分(15,20,7)中,20的位置是索引3。因此,右子树的左子树是15(索引2到2),右子树是7(索引4到4)。
对应的前序部分,右子树的前序是20,15,7。左子树的大小是1(中序中的15个数),所以前序中左子树的部分是15,右子树的部分是7。因此,根20的左子节点是15,右子节点是7。这样,整个树的结构是正确的。
现在,我需要确保代码中的索引分割正确。例如,在构建右子树时:
前序的起始位置是preStart + leftSize +1?或者需要计算左子树的大小?例如,左子树的大小是rootIndex - inStart。对于整个树的根3,左子树的大小是1(rootIndex=1,inStart=0),所以左子树的前序结束位置是preStart + leftSize = 0 +1=1。右子树的前序起始位置是preStart + leftSize +1=1+1=2,结束位置是preEnd=4。这样,右子树的前序是20,15,7,正确。
同样的,在右子树的递归调用中,根是20,在中序中的位置是3(在全局的中序数组中的索引3),而右子树的中序范围是原来的右子树的范围,即inStart=2,inEnd=4。此时,rootIndex=3(在中序中的位置)。左子树的大小为rootIndex - inStart =3-2=1。因此,右子树的左子树的前序范围是preStart+1到preStart+1(假设preStart是2,即右子树的前序起始是2),所以左子树的前序结束是2+1=3,对应的元素是15。右子树的前序起始是3+1=4,元素是7。
因此,递归构建是正确的,最终树的结构应该是:
根3,左子节点9,右子节点20。20的左子节点15,右子节点7。这符合用户给出的前序和中序遍历的结果。
接下来,我需要将这些步骤用C++代码实现。需要注意的是,使用哈希表来快速查找中序的索引。另外,递归函数的参数需要包括前序和中序的数组,以及当前的索引范围。由于C++中数组可以通过指针或vector传递,这里可能需要用索引来标记范围。
可能的代码结构:
定义树节点结构体:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
然后,编写构建函数:
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
// 创建哈希表存储中序遍历的索引
unordered_map<int, int> index_map;
for (int i = 0; i < inorder.size(); ++i) {
index_map[inorder[i]] = i;
}
return buildHelper(preorder, 0, preorder.size() -1, inorder, 0, inorder.size()-1, index_map);
}
private:
TreeNode* buildHelper(vector<int>& preorder, int preStart, int preEnd,
vector<int>& inorder, int inStart, int inEnd,
unordered_map<int, int>& index_map) {
if (preStart > preEnd || inStart > inEnd) {
return nullptr;
}
int rootVal = preorder[preStart];
TreeNode* root = new TreeNode(rootVal);
int rootIndex = index_map[rootVal]; // 在中序中的位置
int leftSize = rootIndex - inStart; // 左子树的大小
// 构建左子树
root->left = buildHelper(preorder, preStart +1, preStart + leftSize,
inorder, inStart, rootIndex -1, index_map);
// 构建右子树
root->right = buildHelper(preorder, preStart + leftSize +1, preEnd,
inorder, rootIndex +1, inEnd, index_map);
return root;
}
};
需要测试这段代码是否正确处理给定的例子。例如,前序数组是[3,9,20,15,7],中序是[9,3,15,20,7]。调用buildTree函数后,应该构建正确的树结构。
可能的测试用例:
当preStart=0,preEnd=4,inStart=0,inEnd=4时:
根是3,rootIndex=1。leftSize=1-0=1。左子树的preStart=1,preEnd=1(preStart+leftSize=0+1=1)。中序的inStart=0,inEnd=0。递归构建左子树,此时preStart=1,preEnd=1,中序的inStart=0,inEnd=0,根是9,左右子树为空。
右子树的preStart=0+1+1=2,preEnd=4。中序的inStart=2,inEnd=4。根是20,rootIndex=3(在中序数组中的索引3)。leftSize=3-2=1。左子树的preStart=2+1=3,preEnd=3(preStart + leftSize=2+1=3?或者原preStart是2,preStart+1=3,preEnd=2+1=3)。中序的inStart=2,inEnd=3-1=2。左子树的根是15,左右子树为空。右子树的preStart=3+1=4,preEnd=4,中序的inStart=4,inEnd=4,根是7,左右子树为空。
因此,最终的树结构正确。
需要注意的是,在传递preStart和preEnd时,是否正确计算。例如,右子树的preStart应该是preStart + leftSize +1,而preEnd保持不变。左子树的preEnd是preStart + leftSize。
现在,检查代码中的参数是否正确:
root->left的preStart是preStart+1,preEnd是preStart + leftSize。因为左子树的前序部分是从preStart+1开始,长度是leftSize。例如,原preStart是0,leftSize是1,所以preEnd是0+1=1。这是正确的。
root->right的preStart是preStart + leftSize +1,即0+1+1=2,preEnd保持4。这样,右子树的前序是2到4,即20,15,7。正确。
同样,在右子树递归时,rootVal是20,rootIndex是3,inStart=2,inEnd=4。leftSize=3-2=1。左子树的preStart是2+1=3,preEnd=2+1=3。对应的前序元素是15。中序的inStart=2,inEnd=3-1=2。正确。
因此,代码应该能正确构建二叉树。
总结,用户的问题可以通过递归方法解决,关键点在于正确分割前序和中序数组的索引,并递归构建左右子树。代码实现需要注意索引的计算,避免越界或错误分割。</think>以下是基于前序遍历和中序遍历构建二叉树的 **C++ 实现**,结合代码注释和示例分析:
---
### **1. 核心原理**
- **前序遍历**:根节点 → 左子树 → 右子树(示例:`[3,9,20,15,7]`)。
- **中序遍历**:左子树 → 根节点 → 右子树(示例:`[9,3,15,20,7]`)。
- **关键步骤**:
1. 前序序列的第一个元素是当前子树的根节点。
2. 在中序序列中找到根节点位置,分割左右子树。
3. 递归构建左子树和右子树。
---
### **2. C++ 代码实现**
```cpp
#include <vector>
#include <unordered_map>
using namespace std;
// 二叉树节点定义
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
// 哈希表快速定位中序遍历中的根节点位置
unordered_map<int, int> index_map;
for (int i = 0; i < inorder.size(); ++i) {
index_map[inorder[i]] = i;
}
return buildHelper(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1, index_map);
}
private:
TreeNode* buildHelper(
vector<int>& preorder, int pre_start, int pre_end,
vector<int>& inorder, int in_start, int in_end,
unordered_map<int, int>& index_map) {
// 终止条件:区间无效时返回空指针
if (pre_start > pre_end || in_start > in_end) return nullptr;
// 前序的第一个元素是当前根节点
int root_val = preorder[pre_start];
TreeNode* root = new TreeNode(root_val);
// 获取根节点在中序中的位置
int root_index = index_map[root_val];
// 计算左子树的节点数量(用于分割前序序列)
int left_size = root_index - in_start;
// 递归构建左子树
root->left = buildHelper(
preorder, pre_start + 1, pre_start + left_size, // 左子树前序区间
inorder, in_start, root_index - 1, // 左子树中序区间
index_map
);
// 递归构建右子树
root->right = buildHelper(
preorder, pre_start + left_size + 1, pre_end, // 右子树前序区间
inorder, root_index + 1, in_end, // 右子树中序区间
index_map
);
return root;
}
};
```
---
### **3. 示例分析**
以输入为例:
- **前序遍历**:`[3,9,20,15,7]`
- **中序遍历**:`[9,3,15,20,7]`
**构建过程**:
1. **根节点**:前序第一个元素 `3`。
2. **分割中序**:
- 左子树中序:`[9]`(根 `3` 左侧)。
- 右子树中序:`[15,20,7]`(根 `3` 右侧)。
3. **递归构建**:
- **左子树**:前序 `[9]`,中序 `[9]` → 直接创建节点 `9`。
- **右子树**:前序 `[20,15,7]`,中序 `[15,20,7]`:
- 根节点 `20`。
- 左子树中序 `[15]`,右子树中序 `[7]`。
- 递归创建左子节点 `15` 和右子节点 `7`。
最终二叉树结构:
```
3
/ \
9 20
/ \
15 7
```
---
### **4. 关键点解释**
- **哈希表优化**:通过 `unordered_map` 快速定位中序根节点位置,时间复杂度降至 $O(n)$。
- **区间分割**:
- 前序左子树区间:`pre_start + 1` 到 `pre_start + left_size`。
- 前序右子树区间:`pre_start + left_size + 1` 到 `pre_end`。
- **终止条件**:当前序或中序区间无效时(起始索引超过结束索引),返回 `nullptr`。
---
### **5. 复杂度分析**
- **时间复杂度**:$O(n)$,每个节点仅访问一次。
- **空间复杂度**:$O(n)$,哈希表存储中序索引,递归栈深度最坏为 $O(n)$(树退化为链表)。
---
### **6. 扩展思考**
- **后序遍历构建**:若已知中序和后序,方法类似,但根节点从后序末尾取。
- **重复元素处理**:当节点值重复时,哈希表无法唯一定位,需其他策略(如上下文分析)。
掌握此方法可解决 LeetCode 题目 [105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)。