中序线索二叉树的创建、线索化和遍历

【问题描述】创建一棵二叉树,接着中序线索化该二叉树,然后编写相应函数遍历该中序线索二叉树

【编码要求】线索二叉树遍历过程中不能使用递归、不能使用栈。

【输入形式】二叉树拓展的前序遍历序列

【输出形式】中序遍历序列

【样例输入】AB#D##CE###

【样例输出】BDAEC

审题:

题目一共有三个小问:

1.创建一颗二叉树。

2.中序线索化该二叉树。

3.基于设置好的线索,中序遍历该二叉树。

那么下面,我们逐个击破这些问题。

首先,创建一颗二叉树。

以一个结构体封装好一个线索二叉树的一个结点所需要的全部属性:

我这里用c++的class封装,因为构造函数挺好用的。

lfrt分别指向左右孩子或者,在中序序列里的前驱(左)后继(右)。LtagRtag就是标记lf和rt指向的到底是孩子结点还是线索,如果Ltag为1,说明此时lf指向的是中序序列里的前驱;如果为0,说明此时,存在左孩子,且lf指向的是左孩子。

class BiNode
{
public:
    BiNode *lf,*rt;
    int Ltag,Rtag;
    Elemtype data;
    BiNode():lf(NULL),rt(NULL),Ltag(0),Rtag(0){}
};

然后,用一个class封装二叉树对象,暂时只封装一个基本属性根节点指针

class Bitree
{
public:
    BiNode *root;
    Bitree():root(NULL){}
    BiNode* create(char *str,int &i);
    void print(BiNode *p);
    void inthread(BiNode *p,BiNode **pre);//线索化
    void printByThread();
};

create函数就是创建二叉树的函数。

现在基于键盘输入的前序遍历序列,创建二叉树。

此处函数接收的两个参数分别指的是前序遍历序列的字符串当前访问字符的下标(传引用是为了把i当成一个全局变量来用)

默认是生成一个新的结点,然后将新节点设置好之后返回。

设置的抽象过程就是先把str[i]赋给新节点的data域,再创建左子树和右子树。

注意此处,只有创建左子树的时候对i进行了+1的操作,而右子树不用。为什么呢?因为在创建过程中,一定是先创建左子树,再创建右子树,而什么时候左子树停止创建呢?那么就是扫描到‘#’这个字符的时候,这种情况下我对i做了+1处理,所以右子树不传++i。当然,你也可以选择把‘#’处的i++删掉,在创建右子树的时候传入++i,也是一样的。

BiNode* Bitree::create(char *str,int &i)
{
    if(str[i]=='#')
    {
        i++;
        return NULL;
    }
    BiNode *t=new BiNode;
    t->data=str[i];
    t->lf=create(str,++i);
    t->rt=create(str,i);
    return t;
}

创建好了一颗未线索化的二叉树之后,就该进入下一个环节,中序线索化该二叉树。

所谓中序线索化,说的通俗一点就是,利用那些没有指向孩子结点的lf和rt,存放中序遍历序列中的前驱和后继。

既然是中序,那么代码基本的结构还是和中序递归遍历一样

只不过有两样东西不同,一个是函数参数多了一个**pre(二级指针的用意同样还是把pre当作全局变量来用,因为前驱!=上一个经过的结点),pre指向的是上一个被操作的结点,也就是前驱;另一个是把访问当前结点变成了中间的一大坨加线索的操作,其实加线索也算是一种广义上的访问吧。

在第一个if语句块中,如果p->lf==NULL为真,就意味着p没有左孩子,也就意味着你要加前驱的线索了,那么pre指向的就是前驱,把它赋给p->lf;因为传的是二级指针,所以对pre做一个*的操作,再把Ltag置为1。

在第二个if语句块中,如果*pre!=NULL&&(*pre)->rt==NULL成立,就意味着在序列里p->data存在前驱,并且p没有右孩子。这时候又该你加后继线索了!不过此处,你并不是对当前结点加后继线索,而是对pre,也就是对p的前驱结点加后继线索,为什么呢?因为程序走到这的时候压根不知道p的后继是谁,但它一定能够知道pre的后继是p。

做完前驱和后继的加线索操作之后,把pre的值更新一下

//中序线索化
void Bitree::inthread(BiNode *p,BiNode **pre)
{
    if(p!=NULL)
    {
        //左
        inthread(p->lf,pre);
        
        //中
        if(p->lf==NULL)
        {
            p->lf=*pre;
            p->Ltag=1;
        }
        if(*pre!=NULL&&(*pre)->rt==NULL)
        {
            (*pre)->rt=p;
            (*pre)->Rtag=1;
        }
        (*pre)=p;//可能存在一个节点的前驱有右孩子,所以这句话应该写在两个if的外面,比如根节点
        
        //右
        inthread(p->rt,pre);
    }
}

中序线索化的工作完成之后,就要解决基于线索遍历二叉树的问题了。

首先,遍历操作大致分为两个步骤,第一个是找到中序遍历序列里第一个被访问的结点,第二个是,根据线索访问剩下的结点

为什么要先找第一个结点的位置呢?因为没有线索指向它。

找到第一个结点位置非常简单,那就是一直向左下方搜,找到第一个Ltag为1的点。Ltag为1就代表的是这个结点没有左孩子,在正常递归的中序遍历里面,也是最先输出最左下方的结点。

事实上,虽然第一个结点的Ltag为1,但是它的lf的值还是NULL,因为它没有前驱。

接着,就开始根据线索遍历二叉树。在循环里做的第一件事输出p指向的data,因为p始终是被移到了下一个待输出的位置。第二件事把p移向它的后继结点,这个时候就要分两种情况讨论:

1.p的rt指向后继结点

2.p的rt指向右孩子

在情况1下,找后继节点非常简单,因为rt指向的就是后继结点。

在情况2下,就要根据中序遍历的特性来思考,因为访问的顺序总是左中右的,所以,后继永远是在p的右子树中的最左下方的结点,根据这一特点,先把p移向p的右孩子,再一直向下,寻找第一个Ltag为1的结点,这和上面的提到的找到第一个结点的操作相通。

当然,如果p->rt==NULL,那么证明,p没有后继也没有右孩子,所以遍历完成,可以return了。

void Bitree::printByThread()
{
    BiNode *p=root;
    while(p->Ltag==0)//找到第一个被遍历的结点
    {
        p=p->lf;
    }
    while(1)
    {
        cout<<p->data;//访问节点

        if(p->Rtag==1)//指针移向后继
        {
            p=p->rt;
        }
        else
        {
            if(p->rt==NULL)
                return;
            else
                for(p=p->rt;p->Ltag==0;p=p->lf);//找到右子树中的最左端
        }
    }
}

以上,三个问题已全部解决,附上完整代码。

#include <iostream>
#define N 100
#define Elemtype char
using namespace std;

class BiNode
{
public:
    BiNode *lf,*rt;
    int Ltag,Rtag;
    Elemtype data;
    BiNode():lf(NULL),rt(NULL),Ltag(0),Rtag(0){}
};

class Bitree
{
public:
    BiNode *root;
    Bitree():root(NULL){}
    BiNode* create(char *str,int &i);
    void print(BiNode *p);
    void inthread(BiNode *p,BiNode **pre);//线索化
    void printByThread();
};

BiNode* Bitree::create(char *str,int &i)
{
    if(str[i]=='#')
    {
        i++;
        return NULL;
    }
    BiNode *t=new BiNode;
    t->data=str[i];
    t->lf=create(str,++i);
    t->rt=create(str,i);
    return t;
}

void Bitree::print(BiNode *p)
{
    if(p!=NULL)
    {
        cout<<p->data;
        print(p->lf);
        print(p->rt);
    }
}

//中序线索化
void Bitree::inthread(BiNode *p,BiNode **pre)
{
    if(p!=NULL)
    {
        inthread(p->lf,pre);
        if(p->lf==NULL)
        {
            p->lf=*pre;
            p->Ltag=1;
        }
        if(*pre!=NULL&&(*pre)->rt==NULL)
        {
            (*pre)->rt=p;
            (*pre)->Rtag=1;
        }
        (*pre)=p;//可能存在一个节点的前驱有右孩子,所以这句话应该写在两个if的外面
        inthread(p->rt,pre);
    }
}

void Bitree::printByThread()
{
    BiNode *p=root;
    while(p->Ltag==0)//找到第一个被遍历的结点
    {
        p=p->lf;
    }
    while(1)
    {
        cout<<p->data;//访问节点

        if(p->Rtag==1)//指针移向后继
        {
            p=p->rt;
        }
        else
        {
            if(p->rt==NULL)
                return;
            else
                for(p=p->rt;p->Ltag==0;p=p->lf);//找到右子树中的最左端
        }
    }
}

int main()
{
    char str[N];
    cin>>str;
    Bitree bt;
    int i=0;
    bt.root=bt.create(str,i);
    //bt.print(bt.root);
    BiNode *pre=NULL;
    bt.inthread(bt.root,&pre);
    bt.printByThread();
    return 0;
}

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值