如何将二叉树线索化

本文探讨了二叉树线索化的原因,旨在优化遍历操作,减少空间和时间消耗。通过在节点中增加前驱和后继字段,或利用空闲的子树指针,来记录遍历顺序。文章提供了前序、中序和后序线索化的代码示例,并强调理解二叉树遍历是学习线索化的前提。线索化过程中需要注意节点指针的用途转换以及遍历顺序的维护。
摘要由CSDN通过智能技术生成

线索化存在的必要原因:对于一颗二叉树,遍历是一个经常性的操作,但是如果我们每次遍历时,都要通过递归或者通过栈实现二叉树的遍历,在遍历时需要进行很多判断,并且一定程度上浪费了一些空间和时间,因此我们考虑是否可以记录下其遍历时其”前驱”和”后继”。
方法:1.对每个二叉树节点增加两个字段”前驱”和”后继”来分别存储遍历过程中它的前一个节点位置和后一个节点位置,这种方式固然是方便,不过也不得不花费更多的空间来存储节点地址,这样一来,可能未能达到优化效果。
2.因为二叉树每个节点的左右子树可能不存在,指向其左右子树的指针为null,这个资源是完全可以被利用的,显然我们可以将节点左右子树指针为null的地方改为指向其”前驱”或者”后继”,但是这样我们就无法区分左右子树指针指向的是”子树”还是”前驱或后继”,所以我们要花费额外的两个bit来区分。
代码及代码说明
首先生成二叉树

typedef enum PointerTag{Link,Thread} PointerTag;//定义标签
typedef struct BiThrNode{
    int data;
    struct BiThrNode *lchirld,*rchild;
    PointerTag LTag,RTag;
}BiThrNode;
//层序生成二叉树
BiThrNode* CreateTree(){
    int arr[]={1,2,3,4,5,6,7};
    BiThrNode *root=(BiThrNode*)malloc(sizeof(BiThrNode));
    root->data=1;
    root->lchirld=root->rchild=NULL;
    BiThrNode** con[7]={};
    int r,l,idx;
    con[0]=&(root->lchirld);
    con[1]=&(root->rchild);
    r=0;
    l=2;
    for(int i=1;i<7;++i){
        BiThrNode *p=(BiThrNode*)malloc(sizeof(BiThrNode));
        p->data=arr[i];
        p->lchirld=p->rchild=NULL;
        *con[r++]=p;
        con[l++]=&(p->lchirld);
        con[l++]=&(p->rchild);
    }
    return root;
}

线索化代码

int DoThr(BiThrNode* pre,BiThrNode* now){
    /*需要前后两个变量,来测试记录前者的"后继"和后者的"前驱"*/
    if(pre){
       if(!pre->rchild){
            pre->RTag=Thread;
            pre->rchild=now;
            cout<<pre->data<<" 的后继是"<<now->data<<endl;
       }
       else pre->RTag=Link;
    }
    if(now){
        if(!now->lchirld){
            now->LTag=Thread;
            now->lchirld=pre;
            if(pre)//由于在刚开始时可能出现pre是null的情况
            cout<<now->data<<" 的前驱是"<<pre->data<<endl;
        }
        else now->LTag=Link;
    }
}

前序线索化

int PreThr(BiThrNode* T){
    BiThrNode *pre=NULL;//记录前一个节点
    BiThrNode *p=T;//此为当前节点
    BiThrNode *visited=NULL;//刚刚访问过的变量
    BiThrNode* stk[10]={};
    int top=0;
    while(top>0||p){
        if(p!=NULL){
            DoThr(pre,p);//注意在前序遍历时,此函数为变化为visit
            pre=p;
            stk[top++]=p;
            if(p->LTag==Thread)p=NULL;//此点需要尤为注意,因为线索化可能会改变p的左子树,而接下来我们判断的就是其左子树,所以不能想遍历时简单判断
            else p=p->lchirld;
        }
        else{
            //此处与前序遍历时相同
            if(stk[top-1]->rchild&&stk[top-1]->rchild!=visited){
                p=stk[top-1]->rchild;
            }
            else{
                visited=stk[--top];
                p=NULL;
            }
        }
    }
}

中序线索化

int InThr(BiThrNode *T){
    BiThrNode *p=T;
    BiThrNode *pre=NULL;
    BiThrNode* stk[10]={};
    int top=0;
    while(top>0||p){
        if(p){
            stk[top++]=p;
            p=p->lchirld;
        }
        else{
            p=stk[--top];
            DoThr(pre,p);//进行线索化,
            pre=p;//及时更新"前驱"
            p=p->rchild;//更新"当前节点"
        }
    }
}

后续线索化

int PostThr(BiThrNode *T){
    BiThrNode *p=T;
    BiThrNode *visited=NULL;
    BiThrNode *pre=NULL;
    BiThrNode* stk[10]={};
    int top=0;
    while(top>0||p){
        if(p){
            //与后续遍历时相同
            stk[top++]=p;
            p=p->lchirld;
        }
        else{
            if(stk[top-1]->rchild&&stk[top-1]->rchild!=visited)p=stk[top-1]->rchild;
            else{
                p=stk[--top];
                visited=p;
                DoThr(pre,p);
                pre=p;
                p=NULL;//注意在此将p赋为null
            }
        }
    }
}

从上述代码中我们可以看出,线索化是在二叉树遍历的基础上进行的,因此在学习线索化之前应该先将二叉树遍历的思路理清楚,这样在学线索化就会很快的学会了。

相比于二叉树的遍历,线索化需要多记录一个pre用来作为当前节点的”前驱”,而当前节点作为”前驱”的后继,然后线索化完此节点后,在将pre和当前节点进行更新,不断重复此过程。

其次,遍历时对节点值打印的动作变为了线索化的动作(这点也不难理解,因为线索化的顺序与遍历节点的顺序是一致的),这一点可能会改变原来对当前节点左右节点存在性的判断,因此进行完线索化之后,需要进一步对其进行分析,以确定下一个节点的位置。

到此为止,线索化的思路已经分享完了,如果各位老铁有对其有着另外的思考,欢迎在文章下面留言~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值