重建二叉树(简单方法)

题目描述(剑指offer第七题)

C++代码

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.size()<1)
            return nullptr;
        TreeNode* root = new TreeNode(pre[0]);
        if(pre.size()==1)
            return root;
        else{//重要
            int mid;
            for(int i=0;i<vin.size();i++)
            {
                if(vin[i]==root->val)
                {
                    mid=i;
                    break;
                }
            }
            if(mid>0)
            {//注意vector初始化vector左开右闭原则->[1,7)等价于pre.begin()+1,pre.begin()+7,只有六个元素
                vector<int> leftpre(pre.begin()+1,pre.begin()+mid+1);
                vector<int> leftvin(vin.begin(),vin.begin()+mid);
                root->left=reConstructBinaryTree(leftpre,leftvin);
            }
            if(mid<vin.size()-1)
            {
                vector<int> rightpre(pre.begin()+mid+1,pre.end());
                vector<int> rightvin(vin.begin()+mid+1,vin.end());
                root->right=reConstructBinaryTree(rightpre,rightvin);
            }
        }
        return root;
    }
};
<think>嗯,用户想通过层次遍历和中序遍历序列来重建二叉树,然后生成先序和后序遍历序列。这看起来有点挑战性,因为通常我们重建二叉树用的是先序和中序,或者后序和中序的组合。层次遍历和中序的组合可能需要不同的方法。 首先,我得回忆一下层次遍历和中序遍历的特点。层次遍历是按层从左到右访问节点,而中序遍历是左根右的顺序。所以,层次遍历的第一个元素肯定是根节点,这一点和先序遍历类似。但接下来的结构如何确定呢? 假设根节点已经确定,那么在中序遍历中找到根节点的位置,左边就是左子树,右边是右子树。接下来,层次遍历中的其他元素需要分配到左子树或右子树中。但层次遍历中的元素顺序并不是按照左右子树严格分开的,所以需要筛选出属于左子树和右子树的节点,然后保持它们在层次遍历中的相对顺序。 比如,假设层次遍历是CBA,中序遍历是BCA。根节点是C,在中序遍历中,B是左子树,A是右子树。然后,层次遍历剩下的B和A需要分配到左右子树。这里B在左子树,A在右子树,所以左子树的层次遍历是B,右子树是A。这样递归下去,就能构建出整个树的结构。 那具体怎么实现呢?可能需要递归的方法。每次用层次遍历的第一个节点作为根,分割中序遍历为左右部分,然后从层次遍历中提取属于左子树和右子树的节点,保持它们的顺序,再递归构建左右子树。 但如何高效地从层次遍历中筛选出左右子树的节点呢?比如,对于当前根节点,中序遍历的左子树部分的所有节点,需要在层次遍历中找到并保留它们的顺序。这一步可能需要遍历层次遍历的剩余元素,检查每个元素是否属于左子树的范围(即中序遍历中根节点左边的部分),然后收集这些元素作为左子树的层次遍历序列,同理处理右子树。 这个过程的时间复杂度可能比较高,因为每次递归都需要遍历层次遍历和中序遍历,但题目中节点数不超过26,所以应该是可行的。 然后,构建完二叉树之后,生成先序和后序遍历就比较简单了,按照各自的遍历方式递归输出即可。 现在,我需要考虑具体的实现步骤。例如,如何分割层次遍历中的左右子树节点,如何递归构建子树,以及如何处理边界情况,比如子树为空的情况。 可能的步骤是: 1. 从层次遍历中取出第一个元素作为根节点。 2. 在中序遍历中找到根节点的位置,分割成左子树和右子树的中序序列。 3. 从层次遍历的剩余元素中,筛选出属于左子树中序序列的元素,保持它们在原层次遍历中的顺序,作为左子树的层次遍历序列。 4. 同样筛选出右子树的元素作为右子树的层次遍历序列。 5. 递归构建左子树和右子树。 6. 在构建完二叉树后,进行先序和后序遍历。 这个过程中,筛选左右子树的层次遍历元素是关键。例如,对于层次遍历中的每一个元素(除了根节点),检查它是否在中序序列的左子树部分,如果是,就加入左子树的层次序列,否则加入右子树的层次序列。这需要遍历中序的左子树部分来确认每个元素的位置。 这样处理的话,每次递归都需要遍历层次序列和中序序列,时间复杂度可能是O(n^2),但因为n不超过26,所以可以接受。 举个例子,样例输入中的第一个测试用例:层次遍历CBA,中序遍历BCA。根节点是C,在中序中位置是2(假设索引从0开始),左子树是B,右子树是A。层次遍历剩下的B和A中,B属于左子树,A属于右子树。所以左子树的层次遍历是B,中序是B;右子树的层次遍历是A,中序是A。递归下去,左子树的根是B,没有左右孩子;右子树的根是A,同样没有孩子。所以构建的树结构是C为根,左孩子B,右孩子A。先序遍历是CBA,后序是BAC。 另一个样例:层次遍历BACDE,中序是DAEBC。根是B,在中序中的位置是3,左边是D, A, E,右边是C。然后层次遍历剩下的元素是A, C, D, E。需要筛选出属于左子树的元素(D, A, E)和右子树的元素(C)。左子树的层次遍历是A, D, E,右子树是C。然后递归处理左子树,根是A,在中序中的位置是1,左子树是D,右子树是E。层次遍历剩下的D和E,D属于左子树,E属于右子树。这样继续构建下去,最终得到正确的树结构。 在代码实现时,需要注意递归的终止条件,当层次遍历为空或中序的起始位置超过结束位置时返回NULL。另外,每次递归需要动态分配左右子树的层次数组,这可能在C语言中需要malloc和free处理,避免内存泄漏。 总结来说,算法的大致流程是递归分割中序序列,并根据层次遍历筛选出对应的子树节点,保持层次顺序,从而构建二叉树。之后进行先序和后序遍历即可得到结果。</think>--- ### 一、算法核心思想 通过层次遍历确定根节点,结合中序遍历分割左右子树,递归构建二叉树: 1. **根节点定位**:层次遍历的第一个元素是当前子树的根节点 2. **子树分割**:在中序遍历中找到根节点位置,分割为左右子树区间 3. **层次过滤**:从层次遍历中筛选属于左右子树的元素,保持原有顺序 4. **递归构建**:对左右子树重复上述过程[^1] --- ### 二、算法实现步骤 #### 1. 二叉树节点定义 ```c typedef struct TreeNode { char data; struct TreeNode* left; struct TreeNode* right; } TreeNode; ``` #### 2. 核心构建函数 ```c TreeNode* buildTree(char* level, int levelSize, char* in, int inStart, int inEnd) { if (levelSize == 0 || inStart > inEnd) return NULL; // 创建根节点 TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode)); root->data = level[0]; root->left = root->right = NULL; // 定位根节点在中序中的位置 int rootPos = -1; for (int i = inStart; i <= inEnd; i++) { if (in[i] == root->data) { rootPos = i; break; } } // 划分左右子树层次序列 int leftCount = 0, rightCount = 0; char* leftLevel = (char*)malloc(levelSize * sizeof(char)); char* rightLevel = (char*)malloc(levelSize * sizeof(char)); for (int i = 1; i < levelSize; i++) { char c = level[i]; int isLeft = 0; // 检查是否属于左子树范围 for (int j = inStart; j < rootPos; j++) { if (in[j] == c) { isLeft = 1; break; } } if (isLeft) leftLevel[leftCount++] = c; else rightLevel[rightCount++] = c; } // 递归构建子树 root->left = buildTree(leftLevel, leftCount, in, inStart, rootPos-1); root->right = buildTree(rightLevel, rightCount, in, rootPos+1, inEnd); free(leftLevel); free(rightLevel); return root; } ``` #### 3. 遍历生成函数 ```c void preOrder(TreeNode* root, char* res, int* idx) { if (!root) return; res[(*idx)++] = root->data; // 根 preOrder(root->left, res, idx); // 左 preOrder(root->right, res, idx); // 右 } void postOrder(TreeNode* root, char* res, int* idx) { if (!root) return; postOrder(root->left, res, idx); // 左 postOrder(root->right, res, idx); // 右 res[(*idx)++] = root->data; // 根 } ``` --- ### 三、完整测试用例 **输入**: ``` 2 CBA BCA BACDE DAEBC ``` **代码逻辑**: ```c int main() { int N; scanf("%d", &N); while (N--) { char level[27], in[27]; scanf("%s %s", level, in); int n = strlen(level); TreeNode* root = buildTree(level, n, in, 0, n-1); char pre[27] = {0}, post[27] = {0}; int preIdx = 0, postIdx = 0; preOrder(root, pre, &preIdx); postOrder(root, post, &postIdx); printf("%s %s\n", pre, post); } return 0; } ``` **输出**: ``` CBA BAC BADEC DEACB ``` --- ### 四、算法分析 | 指标 | 说明 | |--------------|----------------------------------------------------------------------| | 时间复杂度 | $O(n^2)$,每次递归需要遍历层次序列筛选子树元素 | | 空间复杂度 | $O(n)$,递归栈深度与树高相关 | | 关键优化方向 | 使用哈希表存储中序元素位置,将查找操作优化为$O(1)$[^2] | --- ### 五、相关扩展问题 1. **如何验证输入遍历序列的合法性?** - 检查层次遍历与中序遍历元素是否完全匹配 - 验证子树划分时是否出现矛盾(例如:根节点不在中序范围内) 2. **存在重复元素时如何处理?** - 需要额外信息(如节点唯一标识) - 无法仅凭遍历序列唯一确定二叉树结构 3. **非递归实现方案?** - 使用队列管理待处理子树范围 - 维护子树在中序和层次遍历中的索引范围 4. **层次遍历生成算法优化?** - 预先生成中序元素位置映射表 - 使用双指针法减少内存分配次数 [^1]: 递归构建过程通过缩小问题规模实现树结构重建 [^2]: 哈希表优化可将时间复杂度降为$O(n)$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值