C++ 重构二叉树(已知中序遍历和另外一个遍历,重建二叉树)

重构(恢复)二叉树的题目一般会告诉我们二叉树的“前序遍历”和“中序遍历”的序列(序列中一般没有重复的元素),让我们来求二叉树的后序遍历(或者层次遍历);

或者告诉我们二叉树的“后序遍历”和“中序遍历”的序列,让我们来求二叉树的前序遍历(或者层次遍历);

这里需要注意,如果我们只知道一个二叉树 的前序遍历和后序遍历,在一些情况下我们是无法重构出二叉树的,这里不做具体的分析,感兴趣的朋友可以自行百度;

重构二叉树我们一般递归的方法来完成,本篇文章暂不做深层原理的分析,而是给各位提供一种做此类型题目的一个套路模板:

这里先给出结构体BinaryNode来表示树的节点:

struct BinaryNode{
    int val;
    BinaryNode* left,* right;
    BinaryNode(int a):val(a),left(nullptr),right(nullptr){}
};

我们以“前序遍历”+“中序遍历”为例来重构二叉树:

例如:提供给我们前序遍历为:1 2 3 4 5 6 7

                             中序遍历为:3 2 4 1 6 5 7

下面这幅图首先给出了一个大体上的概括:(如果不理解也可直接跳过此幅图,此图仅提供一个大体上的过程概念)

以下是具体步骤:

由前面我们已经了解各种遍历的规律(这里默认各位都已经懂了哈),我们可以确定唯一的二叉树:如下图

首先 ,我们用两个容器vector<int> pre    vector<int> in 来分别保存题目给我们的前序遍历和中序遍历的数据,观察容器中的先序遍历和中序遍历的数据,我们发现先序遍历的第一个元素1对应的就是我们在下面的第二幅图中抽象出来的根节点。

接下来的步骤就很简单了,

(第一步)我们首先要找出先序遍历中元素pre[0]在中序遍历中的位置;

因为我们发现先序遍历中的这个首元素,在这里是1哈;这个1就非常巧的在中序遍历中作为一个“分界线”把前面的部分和后面的部分给分开了,这一分我们就发现这个中序遍历中1前面的序列刚好是我们下面第二幅图中抽象出来的左子树,而1后面的部分刚好是我们抽象出来的右子树;

(第二步)我们需要分别找到左子树的前序遍历、中序遍历以及右子树的前序遍历、中序遍历; 

我们为什么要找到它们呢?是因为我们如果找到它们的前序和中序遍历,我们是不是就可以对它们再次调用重构二叉树的函数(这是一个典型的递归模型)

我们怎样找到子树的前序和中序遍历呢,(其实就是对原来的前序遍历和中序遍历进行截断)

我们可以用一个计数器int count=0 来实现

用一个while()循环:while(in[count]!=pre[0]) count++;   

在这个例子中我们得到 count=3 时,循环会停下来,我们是不是就确定了在中序遍历中这个1前面的左子树有多少个元素,根据左子树的前序遍历和中序遍历的元素数目相等,我们也可以在pre中从下标为0的元素的后面那个元素开始数count个元素,这样就得到了左子树的前序遍历的序列。

在此之前我们可以先分别用四个容器:l_pre(这个容器用来保存左子树的前序遍历)l_in(这个容器用来保存左子树的中序遍历)  r_pre(这个容器用来保存右子树的前序遍历)  r_in(这个容器用来保存右子树的中序遍历)

 vector<int> l_pre,l_in,r_pre,r_in;
        for(int i=0;i<count;i++){
            l_pre.push_back(pre[i+1]);
            l_in.push_back(in[i]);
        }
        for(int i=count+1;i<l;i++){
            r_pre.push_back(pre[i]);
            r_in.push_back(in[i]);
        }

大家课以参照上述代码自己推一下,很容易就能得到。

注意:这个方法自我感觉理解起来比较好,但是有一个致命的缺点就是在每次递归过程中,它都会重新创建四个容器,大大增加了空间的开销。因此我们在写重构函数时我们最好把参数pre 和 in 都设置为引用,这样不用调用拷贝构造,在一定程度上减少空间的开销。

(第三步)我们需要创建树节点来保存该节点的值了(其实这一步可以开始就做了)

BinaryNode* tmp=new BinaryNode(pre[0]);

直接用new函数动态开辟一个临时节点;

(第四步)分别对创建出来的节点的左子树和右子树调用重构函数;

        tmp->left=construct(l_pre,l_in,l_pre.size());
        tmp->right=construct(r_pre,r_in,r_pre.size());

(第五步)别忘了要返回我们的tmp指针;

        return tmp;

重构函数的返回值以及参数的选用都很重要;

返回值我们需要设置为树节点的指针:BinaryNode* 

在函数的参数中除了很重要的前序和中序遍历序列,另外一个很重要的参数就是前序和中序遍历的序列的长度(这里我们用L来表示);如下就是我们的重构函数的返回值和参数的一个具体情况;

BinaryNode* construct(vector<int> &pre,vector<int>& in,int l)

为什么要多设置这样一个参数呢?因为这个参数的作用呢就是判断我们的递归何时停止。

它可以分为3种情况哈:

(第一种)当L>1时,这种情况就是最正常的一种,我们循环上述所说的五个步骤;

(第二种)当L=1时,显而易见,当L=1时表明前序和中序遍历中都只有一个元素了,这个时候我们就简单但的创建一个BinaryNode临时变量,把剩下的这个元素作为它的值,再返回这个临时变量的指针就好;

(第三种)当L=0时,在我上述举的这个例子中没有这种情况,但是这种情况是必不可少的,这里留给各位去探索什么情况下会出现L=0;

改变一下顺序,我们把L=0 和 L=1 的判断提前,得到如下图所示的代码:

    if(l==0) return nullptr;

    if(l==1){
        BinaryNode* tmp=new BinaryNode(pre[0]);
        return tmp;
    }

    else{
        BinaryNode* tmp=new BinaryNode(pre[0]);
        int count=0;
        while(in[count]!=pre[0]) count++;
        vector<int> l_pre,l_in,r_pre,r_in;
        for(int i=0;i<count;i++){
            l_pre.push_back(pre[i+1]);
            l_in.push_back(in[i]);
        }
        for(int i=count+1;i<l;i++){
            r_pre.push_back(pre[i]);
            r_in.push_back(in[i]);
        }
        tmp->left=construct(l_pre,l_in,l_pre.size());
        tmp->right=construct(r_pre,r_in,r_pre.size());
        return tmp;
    }

至此我们的重构二叉树的一个代码实现就已经全部讲解完了。

以下给出完整的代码实现:

#include<iostream>
#include<vector>
using namespace std;
struct BinaryNode{
    int val;
    BinaryNode* left,* right;
    BinaryNode(int a):val(a),left(nullptr),right(nullptr){}
};

BinaryNode* construct(vector<int> &pre,vector<int>& in,int l){
    if(l==0) return nullptr;
    if(l==1){
        BinaryNode* tmp=new BinaryNode(pre[0]);
        return tmp;
    }
    else{
        BinaryNode* tmp=new BinaryNode(pre[0]);
        int count=0;
        while(in[count]!=pre[0]) count++;
        vector<int> l_pre,l_in,r_pre,r_in;
        for(int i=0;i<count;i++){
            l_pre.push_back(pre[i+1]);
            l_in.push_back(in[i]);
        }
        for(int i=count+1;i<l;i++){
            r_pre.push_back(pre[i]);
            r_in.push_back(in[i]);
        }
        tmp->left=construct(l_pre,l_in,l_pre.size());
        tmp->right=construct(r_pre,r_in,r_pre.size());
        return tmp;
    }
}
// void postOrder(BinaryNode* root){
//     if(root==nullptr) return;
//     postOrder(root->left);
//     postOrder(root->right);
//     cout<<root->val<<" ";
// }
int main(){
    int l;
    cin>>l;
    vector<int> pre(l,0),in(l,0);
    for(int i=0;i<l;i++) cin>>pre[i];
    for(int i=0;i<l;i++) cin>>in[i];
    BinaryNode* root=nullptr;
    root=construct(pre,in,l);
    // postOrder(root);
    return 0;
}

代码中注释掉的地方是题目会要求我们以什么样的遍历顺序来输出,我们就正常写就好了,像上述代码中注释掉的部分就是实现一个二叉树的后序遍历输出。

到此我给出的例子就描述完成了,如果题目给我们“后序遍历”+“中序遍历”其实也是一样的,就是把前序遍历给倒过来,这个的实现就留给各位读者了;

希望我的这个我觉得较为简单的理解方法可以帮到大家;当然肯定会有更好的办法,但是我认为这个办法实现起来会比较容易(不考虑时间和空间的开销的话);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值