二叉树的线索化(摘自天勤数据结构高分笔记)

/************************************************************************/
/* 
    二叉树的线索化:二叉树的线索化可以将二叉树非递归遍历算法中的用户栈也给省掉,进一步提高效率
    中序线索二叉树的结点结构如下:
         ______________________________________
        | lchild | Ltag | data | Rtag | Rchild |
      ---------------------------------------
      在二叉树线索化的过程当中会把二叉树中的空指针利用起来作为寻找点钱结点前驱和后继的线索,这样就出现了一个问题,即线索和树中原有指向孩子结点的指针就无法
      区分。上述的结点设计就是为了区分这两类指针,其中Ltag和Rtag为标识域,其含义如下:
        1、若Ltag==0,则表示lchild为指针,指向结点的左孩子;若Ltag==1,则表示lchild为线索,指向结点的直接前驱
        2、若Rtag==0,则表示rchild为指针,指向结点的有孩子;若Rtag==1,则表示rchild为线索,指向结点的直接后继
        对应的二叉树的结点定义如下:
*/
/************************************************************************/

typedef struct TBTNode
{
    char data;
    int Ltag, Rtag;    //线索标记
    struct TBTNode *lchild;
    struct TBTNode *rchild;
}TBTNode;

/************************************************************************/
/* 
线索二叉树可以分为前序线索二叉树、后序线索二叉树、中序线索二叉树。本处一后续线索二叉树为例
二叉树中序线索化思路分析:
    1、既然要对二叉树进行中序线索化,首先要有个中序遍历的框架。这里采用二叉树递归遍历算法,在遍历过程中连接上合适的线索即可
    2、线索化的规则是:左线索指针指向当前结点在中序遍历序列中的前驱结点,右线索指针指向后继结点。因此我们需要一个指针P指向
    当前正在访问的结点,pre指向P的前驱结点,P的左线索如果存在则让其指向pre,pre的右线索如果存在则让其指向P,因为P是pre的后继
    结点,这样就完成了一对线索的连接。按照这样的规则一直进行下去,当整棵二叉树遍历完成的时候,线索化也就完成了。(天勤数据结构
    高分笔记P151)
    3、上一步中保持pre始终指向p的前驱的具体过程是:当P要离开一个访问过的结点时,pre指向P;当P来到一个新结点时,pre显然指向的
    是此时p所指向结点的前驱结点
    通过中序遍历对二叉树进行线索化的递归算法如下:
*/
/************************************************************************/

void InThread(TBTNode *P, TBTNode *&pre)
{
    if (P != NULL)
    {
        InThread(P->lchild, pre);        //递归,左子树进行线索化
        if (P->lchild == NULL)
        {
            //建立当前结点的前驱线索
            P->lchild = pre;
            P->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL )
        {
            //建立前驱结点的后继线索
            pre->rchild = P;
            pre->Rtag = 1;
        }
        pre = P;                         //pre指向当前的P,作为P将要指向的下一个结点的前驱结点指示指针
        P = P->rchild;                    //P指向一个新结点,此时pre和P分别指向的结点想成了一个前驱后继对,为下一次线索的连接做准备
        InThread(pre->rchild, pre);       //递归,右子树线索化
    }
}
/************************************************************************/
/* 
    if (P->lchild == NULL)
        {
            //建立当前结点的前驱线索
            P->lchild = pre;
            P->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL )
        {
            //建立前驱结点的后继线索
            pre->rchild = P;
            pre->Rtag = 1;
        }
        pre = P;                         //pre指向当前的P,作为P将要指向的下一个结点的前驱结点指示指针
        P = P->rchild;                    //P指向一个新结点,此时pre和P分别指向的结点想成了一个前驱后继对,为下一次线索的连接做准备
这一段代码就相当于二叉树遍历算法模板中的visit()函数
*/


/************************************************************************/
//通过中序建立中序线索二叉树的主程序如下:

void CreateInThread(TBTNode *root)
{
    TBTNode *pre = NULL;        //前驱结点指针
    if (NULL != root)
    {
        InThread(root, pre);
        pre->rchild = NULL;     //非空二叉树线索化
        pre->Rtag = 1;        //后处理中序最后一个结点

    }
}


//遍历中序线索化二叉树:访问运算主要是为遍历中序线索二叉树服务的,这种遍历不再需要栈,因为它利用了隐含在线索二叉树中的前驱和后继信息
//求以P为根结点的中序线索二叉树中,中序序列下的第一个结点的算法如下:

TBTNode *First(TBTNode *P)
{
    while (P->Ltag == 0)
        P = P->lchild;        //最坐下结点不一定是叶节点(可能该结点只有右孩子没有左孩子)
    return P;
}
//求在中序线索二叉树中,结点P在中序下的后继结点的算法如下:
TBTNode *Next(TBTNode *p)
{
    if (0 == p->Rtag)
        return First(p->lchild);
    else
        return p->rchild;    // Rtag == 1,直接返回后继线索
}


/************************************************************************/
/* 
    如果把函数First中的Ltag和lchild分别换成Rtag和rchild,同时把函数名换成Last,则可以得到求中序序列下最后一个结点的函数Last();
    如果把函数Next中的Rtag和rchild换成Ltag和lchild,并同时把函数First()换成Last(),再把函数名Next改成Prior则可以求得中序序列下前驱结点的函数Perior()
*/
/************************************************************************/
//最后很容易的就可以写出在中序线索二叉树上执行中序遍历的算法

void Visit(TBTNode *p)
{

}
void Inorder(TBTNode *root)
{
    for (TBTNode *p = First(root); p != NULL; p = Next(p))
        Visit(p);
}

//前序线索二叉树:前序线索化代码和中序线索化代码极为相似,最大的区别就是把连接线索的代码提到了两个递归入口的前面,这还是符合先序递归遍历的框架

void PreThread(TBTNode *p, TBTNode *&pre)
{
    if (p != NULL)
    {
        if (p->lchild == NULL)
        {
            p->lchild = pre;
            p->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL)
        {
            pre->lchild = p;
            pre->Rtag = 1;
        }
        pre = p;
        //注意在这里递归入口处有限制条件,当且仅当做指针不是线索时才继续递归
        if (p->Ltag == 0)
            PreThread(p->lchild, pre);
        if (p->Rtag == 0)
            PreThread(p->rchild, pre);
    }
}


//在前序线索二叉树上执行前序遍历的算法如下:

void Preorder(TBTNode *root)
{
    if (NULL != root)
    {
        TBTNode *p = root;
        while (p != NULL)
        {
            while (p->Ltag == 0)    //左指针不是线索,则变访问边左移
            {
                Visit(p);
                p = p->lchild;
            }
            Visit(p);        //此时左指针必为线索,但还没有被访问,则访问
            p = p->rchild;    //此时p的左孩子不存在,则右指针若非空,则不论是否为线索都指向其后继
        }
    }
}

//后序线索二叉树:后续线索化代码和中序线索化代码即为相似,最大的区别就在于把连接线索的代码放到了两个递归入口的后边。这也符合后序遍历的框架

void PostThread(TBTNode *p, TBTNode *&pre)
{
    if (p != NULL)
    {
        PostThread(p, pre);        //递归,左子树线索化
        PostThread(p, pre);        //递归,右子树线索化
        if (NULL == p->lchild)
        {
            p->lchild = pre;
            p->Ltag = 1;
        }
        if (pre != NULL && pre->lchild == NULL)
        {
            pre->rchild = p;
            pre->Rtag = 1;
        }
        pre = p;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_15054345/article/details/84071191