数据结构(树和二叉树 Part3 线索二叉树)

线索二叉树      

    

           

           

              

       

        

            

             

树结点的结构 

下面是代码的实现:

树的结构发生了变化

struct TreadedBiNode
{
    QString ch;
    TreadedBiNode * lchild;//指向右孩子
    int ltag;//ltag为0时指向该结点的左孩子,为1时指向该结点的前驱
    TreadedBiNode * rchild;//指向左孩子
    int rtag;
};

线索化 

线索化最合适的还是在遍历过程中进行前后驱的赋值比较合适

整理一下线索化过程的思路,一个结点的前驱:指向自己的左子树,后驱指向自己的右子树

按照遍历的方式不同,代码不同

中序遍历,先遍历左子树,再遍历根,再到右子树,

整个过程需要增加一个指针,指向刚才访问过的结点,也就是根的左子树,在访问到根的时候,让根的前驱为左子树

在访问到右子树的时候,刚才访问过的结点就是根,此时再让右子树的前驱指向根。

拿一个最简单的树作为例子:

线索化的操作:该结点左子树空余,将其指向前驱,右子树空余,指向后驱。 

中序遍历,递归过程中最先到达的结点一点是H,此时的pre(指向最先访问的结点的指针)还是nullptr初始状态。

此时,可以给H的前驱赋值了,指向pre,鉴于pre指向空,不进行操作,此时更新pre指针,让其指向H

然后随着遍历的发生,回到了根D处,D有左子树,所以不进行操作,而H没有右子树,此时pre又指向H,所以让H的右子树做后驱指向D。同时更新pre,让其指向D。

然后随着遍历的发生,回到了I处,pre此时指向D,D有右子树,所以不进行操作,I没有左子树,所以指向前驱D(此时由pre代替),I没有右子树,后驱本身就是nullptr,结束遍历即可。

整个过程中有两个指针,一个是指向该结点本身的函数参数,一个是指向上一个访问过结点的结点指针。

                                   

                                          

                                                         

综上,还是一个递归调用的过程,过程中需要一个pre指针指向前一个访问到的结点,之后判断目前结点的左子树和上一个结点的右子树情况,再次进行各项赋值操作,具体代码如下:

void InTreaded(TreadedBiNode * root)
{
    if(nullptr==root)
    {
        return;
    }
    else
    {
        InTreaded(root->lchild);
        if(root->lchild == nullptr)//左子树为空,可以放置前驱
        {
            root->ltag = 1;
            root->lchild = pre;
        }
        if(nullptr != pre)
        {
            if(pre->rchild == nullptr)//判断上一个访问的结点的右子树是不是空,是空的话,防止后驱
            {
                pre->rtag = 1;
                pre->rchild = root;
            }

        }
        pre = root;
        InTreaded(root->rchild);
    }
}

从这儿也能看出,创建树最好的遍历方式是先序,从根出发,输入字符串和匹配代码最合适

线索化也是,最好是使用中序遍历,因为需要两个指针一起协调工作,结构和原理都类似链表,所以从左到右或者从右到左是最合适的方式,从中间到两边就不那么方便了。

增加头指针

 完成线索化后,模仿双向链表,给一个头指针,如下图(代码比较简单不在说明)

                          

void InitHeader(TreadedBiNode * header,TreadedBiNode * Root)
{
    if(nullptr == header||nullptr == Root)
        return;
    //①左子树最左侧的叶子结点的前驱指针,指向头指针,②头指针的前驱指向根,③头结点的后驱指针指向右子树最右侧的叶子结点,④右子树最右侧的叶子结点的后驱,指向头指针
    header->lchild = Root;//②头指针的前驱指向根
    header->ltag = 0;

    TreadedBiNode * pCurrent = Root;
    while(pCurrent->ltag != 1)
        pCurrent = pCurrent->lchild;
    pCurrent->lchild = header;//①左子树最左侧的叶子结点的前驱指针
    pCurrent = Root;
    while(pCurrent->rtag != 1)
        pCurrent = pCurrent->rchild;
    pCurrent->rchild = header;//④右子树最右侧的叶子结点的后驱,指向头指针
    pCurrent->rtag = 1;

    header->rchild = pCurrent;//③头结点的后驱指针指向右子树最右侧的叶子结点
}

具体代码执行情况 

进行实际操作,以上述的树为例:

                      

使用先序遍历法创建树,输入

    char * InPut = "ABDH##I##EJ###CF##G##";

    TreadedBiNode * MyRoot = CreateTree(InPut);

 创建的时候,直接把lflag和tflag全部初始化。创建接口如下:

TreadedBiNode * CreateTree(char * leaf)
{
    if(nullptr == leaf)
        return nullptr;
    if(position>( (int)strlen(leaf)-1)||strlen(leaf) == 0)
        return nullptr;
    TreadedBiNode * root = new TreadedBiNode;
    if(leaf[position] == '#')
    {
        root = nullptr;
        position++;
        return root;
    }
    else
    {
        root->ch = leaf[position];
        position++;

        root->lchild = CreateTree(leaf);

        if(root->lchild == nullptr)
            root->ltag = 1;//可以放前驱
        else
            root->ltag = 0;//有左孩子

        root->rchild = CreateTree(leaf);

        if(root->lchild == nullptr)
            root->rtag = 1;//可以放前驱
        else
            root->rtag = 0;//右故事

        return root;
    }

}

创建完成之后进行线索化和增加头指针

    InTreaded(MyRoot);//线索化
    TreadedBiNode * MyHeader = new TreadedBiNode;//做一个头指针
    MyHeader->lchild = nullptr;
    MyHeader->rchild = nullptr;
    MyHeader->ltag = 1;
    MyHeader->rtag = 1;
    InitHeader(MyHeader,MyRoot);//连接头指针

完成后,传递头指针即可完成顺序和逆序的遍历 

遍历

先看一下遍历的操作和结果:

    Foreach(MyHeader);
    qDebug()<<"反向打印";
    Anti_Foreach(MyHeader);

输出:

  

遍历程序如下:

//遍历
void Foreach(TreadedBiNode * header)
{
    if(nullptr == header)
        return;
    TreadedBiNode * pCurrent = header->lchild;//第一个元素——根
    while(pCurrent != header)
    {
        while(pCurrent->ltag != 1)
            pCurrent = pCurrent->lchild;
        qDebug()<<pCurrent->ch;//链表首地址
        while(pCurrent->rtag == 1&&pCurrent->rchild!=header)//调用后续指针
        {
            pCurrent = pCurrent->rchild;//
            qDebug()<<pCurrent->ch;
        }
        pCurrent = pCurrent->rchild;
    }

}

                           

整个过程中都是以上图作为基本遍历思路,顺序则是按照中序遍历进行打印,逆序就是反中序打印 

  1. 首先找到最左侧的左叶子结点(H),也是中序的开始,打印
  2. 查看后驱,指向根,打印
  3. 根有右孩子,指向右孩子,没有,指向后驱,操作都是一样的 ( pCurrent = pCurrent->rchild;

完成3操作后,左叶子结点和根都打印了,右侧是否是叶子结点无法判断,也有可能是新的根,所以重复以上操作,找到右孩子结点的左叶子结点,如果有重复输出即可,没有,自己就是右叶子结点,输出,然后重复2,查看后驱,打印即可。

整个过程就是一个从左向右的打印过程,指针不断被更新指向,逆向打印是一样的,先到的最右侧的右叶子结点(G)

从右向左打印,刚才是右侧结点无法判断,需要循环判读,此处就变成了左侧结点无法判断。

逆序遍历

void Anti_Foreach(TreadedBiNode * header)
{
    if(nullptr == header)
        return;
    TreadedBiNode * pCurrent = header->lchild;//第一个元素——根
    while(pCurrent != header)
    {
        while(pCurrent->rtag != 1)
            pCurrent = pCurrent->rchild;//到达最右侧
        qDebug()<<pCurrent->ch;//链表首地址
        while(pCurrent->ltag == 1&&pCurrent->lchild!=header)//调用后续指针
        {
            pCurrent = pCurrent->lchild;//
            qDebug()<<pCurrent->ch;
        }
        pCurrent = pCurrent->lchild;
    }

}
发布了85 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41605114/article/details/104690849