重建二叉树

题目

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中不含有重复地数字。例如输入的前序遍历序列{1 , 2 , 4 , 7 , 3, 5 , 6 , 8}和中序遍历序列{4 , 7 , 2 , 1 , 5 , 3 , 8 , 6},则重建出下图1所示的二叉树并输出它的头结点。

        (1)
      /     \
    (2)     (3)
    /      /   \
  (4)    (5)   (6)
   \            /
   (7)         (8)

        图1

分析

在二叉树的前序遍序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。因此需要扫描中序遍历序列,才能找到根结点的值。

这里写图片描述

如上图,前序遍历序列的第一个数字1就是根结点的值。扫面中序遍历序列,就能确定根结点的值的位置。根据中序遍历序列特点,在根结点的值1前面的数字都是左子树结点的值,位于1后面的数字都是右子树结点的值。
由于中序遍历序列中,有3个数字是左子树结点的值,因此左子树总共有3个有3个做子结点。同样,在前序遍历序列中,根结点后面的3个数字就是3个左子树的结点的值,在后面的所有数字都是右子树结点的值。这样就在前序和中序这两个遍历中,分别找到左右子树的对应的子序列。

代码

#include<stdio.h>
#include<iostream>
#include <exception>

using namespace std;

typedef int ElemType;




typedef struct BiTNode {

    ElemType data;                              //结点的数据域
    struct BiTNode *lchild, *rchild;            //左右孩子
}BiTNode, *BiTree;


/*递归的调用constructCore去构造左右子数*/
BiTree constructCore(int* sPreorder, int* ePreorder, int* sInorder, int* eInorder){ 

    //前序遍历序列中的第一个数字是根结点
    int rootValue = sPreorder[0];
    BiTNode* root = new BiTNode();
    root->data = rootValue;
    root->lchild = NULL;
    root->rchild = NULL;

    //判断是否只有一个根结点
    if(sPreorder == ePreorder) {

        if(sInorder == eInorder && *sPreorder == *sInorder)
            return root;
        else
            throw std::exception("Invaild input");

    }


    //在中序遍历寻找根结点的值
    int* rootInorder = sInorder;
    while(rootInorder <= eInorder && *rootInorder != rootValue) 
        rootInorder++;

    if(rootInorder == eInorder && *rootInorder != rootValue)
        throw std::exception("Invalid input");

    //左子树的数量
    int  leftLength = rootInorder - sInorder;
    int* leftPreorderEnd = sPreorder + leftLength;
    if(leftLength > 0) {

        //构建左子树
        root->lchild = constructCore(sPreorder+1,leftPreorderEnd,sInorder,rootInorder - 1);

    }

    if(leftLength < ePreorder - sPreorder) {

        //构建右子树
        root->rchild = constructCore(leftPreorderEnd+1,ePreorder,rootInorder+1,eInorder);

    }

    return root;




}


BiTree construct(int* preorder, int* inorder , int length) {


    if(preorder == NULL || inorder == NULL || length <= 0) 

        return NULL;

    return  constructCore(preorder, preorder+length-1,inorder,inorder+length-1);

}

/*后序遍历递归函数*/
void postOrderTraverse(BiTree T) {

    if(T) {
        postOrderTraverse(T->lchild);
        postOrderTraverse(T->rchild);
        cout<<T->data;

    }

}



void main () {

    int preorder[8] = {1, 2, 4, 7, 3, 5, 6, 8};
    int inorder[8] = {4, 7, 2, 1, 5, 3, 8, 6};

    BiTNode* root = new BiTNode;

    root = construct(preorder, inorder, 8);

    printf("后序遍历测试\n");
    postOrderTraverse(root);
    cout<<"\n\n";


}

截图

这里写图片描述

本研究利用Sen+MK方法分析了特定区域内的ET(蒸散发)趋势,重点评估了使用遥感数据的ET空间变化。该方法结合了Sen斜率估算器和Mann-Kendall(MK)检验,为评估长期趋势提供了稳健的框架,同时考虑了时间变化和统计显著性。 主要过程与结果: 1.ET趋势可视化:研究利用ET数据,通过ET-MK和ET趋势图展示了蒸散发在不同区域的空间和时间变化。这些图通过颜色渐变表示不同的ET水平及其趋势。 2.Mann-Kendall检验:应用MK检验来评估ET趋势的统计显著性。检验结果以二元分类图呈现,标明ET变化的显著性,帮助识别出有显著变化的区域。 3.重分类结果:通过重分类处理,将区域根据ET变化的显著性进行分类,从而聚焦于具有显著变化的区域。这一过程确保分析集中在具有实际意义的发现上。 4.最终输出:最终结果以栅格图和png图的形式呈现,支持各种应用,包括政策规划、水资源管理和土地利用变化分析,这些都是基于详细的时空分析。 ------------------------------------------------------------------- 文件夹构造: data文件夹:原始数据,支持分析的基础数据(MOD16A2H ET数据 宁夏部分)。 results文件夹:分析结果与可视化,展示研究成果。 Sen+MK_optimized.py:主分析脚本,适合批量数据处理和自动化分析。 Sen+MK.ipynb:Jupyter Notebook,复现可视化地图。
<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、付费专栏及课程。

余额充值