重建二叉树解题笔记


题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字,则重建二叉树并返回。

牛客示例:

输入:

[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]

返回值:

{1,2,5,3,4,6,7}

解题思路:

此题可用递归或非递归方法解,按先序的顺序,利用中序的数据辅助进行二叉树的重建。

首先利用先序与后序的特性,可以得知先序的第一个结点就是根节点,而对应到中序顺序中可以发现在根节点左边的所有结点都在根节点的左子树上,右边所有结点都在根节点右子树上;如示例的根节点1,左边3、2、4都在1的左子树上,右边的6、5、7都在右子树上。

对于每个子树,在其相应的所有结点所在的范围内,上面的规律同样适用。

按照这个规律,我们可以每次找到根节点,再利用根节点进而推出其它的结点。

递归:

思路:

递归方法很简单,利用上面的规律,在函数中我们找到根节点后建立根节点,再从中序顺序的根节点左边的范围内的数据继续调用函数去重建左子树的结点,接着同样从右边范围内的数据调用函数去重建右子树的结点;中间要记住每次建立结点的结点在中序的位置,然后从左边界到根节点这个范围调用函数计算左子树,根节点到右边界范围调用函数计算右子树。

如示例:首先将左边界标到中序的最左边3,有边界标到最右边元素的后一个位置既数组的末尾,前序标签标在前序数组首元素1;然后在左右边界范围内对前序标签进行查找,找到1在下标为3的位置,重建结点并使前序标签前进一位(每重建一个结点都要加一次前序标签,因为是按照前序的顺序进行的结点重建)。然后判断根节点左边是否有元素,有则递归左子树,范围改为左边界到根节点的位置;接着同样递归右子树,范围为根节点右边到右边界。
在这里插入图片描述

代码:

    vector<int>::iterator pre_begin;//前序标签
    void build_tree(TreeNode** root_Node,vector<int>::iterator vin_begin,vector<int>::iterator vin_end)//第二个参数是计算子树的左边界,第三个是右边界
    {
        *root_Node=new TreeNode(*pre_begin);
        vector<int>::iterator vin_mid=find(vin_begin,vin_end,*pre_begin);//查找根节点的位置
        pre_begin++;
        if(vin_begin!=vin_mid)
            build_tree(&(*root_Node)->left,vin_begin,vin_mid);
        if(vin_mid+1!=vin_end)
            build_tree(&(*root_Node)->right,vin_mid+1,vin_end);
        return;
    }
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        TreeNode* tree_p;
        if(pre.begin()==pre.end())
            return NULL;
        pre_begin=pre.begin();
        build_tree(&tree_p,vin.begin(),vin.end());
        return tree_p;
    }

非递归:

思路:

规律与递归相似,但实现的方法不同;非递归要使用栈与循环,每一次循环会将所要计算的子树的最左边的分支上的所有结点计算出来,并将重建的结点压入栈,可以发现,中序的第一个元素结点就是二叉树最左边分支上的最下边的结点,也就是说在计算这些结点的时候只要检索到了中序的范围内的最左边界的结点,即代表已经找到了相应的子树的最左分支的所有结点。

例如在示例中,先将左边界设为中序的首个元素位置(因为整个树包含从首元素到末尾元素的所有结点),第一轮将根节点1压入栈,然后前序标签前进一位(与递归方法一样,建立一个节点前序标签就要前进一位),找到左边分支的第二个结点2,同样入栈,最后找到3,此时找到了左边分支的所有结点,然后出栈:将3出栈,同时左边界加一(因为这个子树的根节点3和3的左子树已经检索完了,之后没有了任何用处)那么从左边界(节点3)到栈顶(也就是上一个最左边分支的结点2)之间的所有元素都是结点3的右子树的结点。这里我们发现没有(如果有的话就建立右子树的根节点并继续循环),那么跳过并将指向结点指针也与栈顶位置保持一致,同时由于3的右子树没有结点,下一次循环的计算左子树的操作也同样跳过了,则继续出栈:左边界加一(这里就是排除节点2了,因为以3为根节点的子树已经检索完了,那么节点2也就同样没有任何用处了),将2出栈并从左边界(节点2)到栈顶(节点1也就是最外层树的根节点)检索2的右子树,如此循环…

然后按照上面的思路将栈按顺序一个个将结点出栈,同时计算每个结点的右子树,如果存在右子树则建立右子树的根节点,然后进行下一次循环(计算最左边分支,退栈,检测右子树…)。

遍历的顺序与递归一样,都是前序遍历,所以就懒得画图了。

代码:

TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        vector<int>::iterator pre_mark;
        vector<vector<int>::iterator> vin_mark;//用于存储根节点在中序数组中位置的栈
        vector<int>::iterator left_margin;//左边界指针
        left_margin=vin.begin();
        TreeNode* tree_p;
        if(pre.begin()==pre.end())
            return NULL;
        map<int,TreeNode*> node_p;//这个map用于使树节点指针回退
        pre_mark=pre.begin();
        vin_mark.push_back(vin.end());
        tree_p=new TreeNode(10001);//先随机存一个节点,也可以先将第一个根节点存进来,然后后面相应调整即可
        while(pre_mark!=pre.end())//遍历完所有节点则结束循环
        {
            while(vin_mark.back()!=left_margin)//计算左子树,左子树节点数目取决于左边界和栈顶节点之间的节点数
            {
                vin_mark.push_back(find(left_margin,vin_mark.back(),*pre_mark));
                tree_p->left=new TreeNode(*pre_mark);
                tree_p=tree_p->left;
                node_p.insert(map<int, TreeNode*>::value_type(*pre_mark,tree_p));//记录每个节点的指针,用于回退
                pre_mark++;
            }
            left_margin++;//退栈操作
            vin_mark.pop_back();
            if(left_margin!=vin_mark.back())//建立右子树的根节点
            {
                tree_p->right=new TreeNode(*pre_mark);
                tree_p=tree_p->right;
                node_p.insert(map<int, TreeNode*>::value_type(*pre_mark,tree_p));
                vin_mark.push_back(find(left_margin,vin_mark.back(),*pre_mark));
                pre_mark++;
            }
            else
                tree_p=node_p[*vin_mark.back()];
        }
        tree_p=node_p[pre[0]];
        return tree_p;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值