线索二叉树

线索二叉树

遍历二叉树是以一定的规则将二叉树排列成一个线性序列,是将一个非线性结构进行线性化的操作,使每个元素只有一个直接前驱和直接后继.
对于一个二叉树,每个节点的只能知道其后继信息不知道其前驱的信息,要想知道就需要遍历,能不能不遍历就知道前驱的信息呢?当然可以:
1. 一个简单的方法即直接在每个节点上加上前驱和后继的信息即可,但是会使得结构存储密度降低.
2. 第二种方法就是由于n个结点则有n+1个空链域,可以利用这些空链域存放节点前驱和后继的信息

*lchildltagdatartag*rchild

ltag =

{0,1,lchild lchild   { 0 , l c h i l d  域指示结点的左孩子 1 , l c h i l d  域指示结点的前驱 

rtag =
{0,1,rchild rchild   { 0 , r c h i l d  域指示结点的右孩子 1 , r c h i l d  域指示结点的后继 

结点中这种指向前驱和后继的指针叫做线索,
加上线索的二叉树叫做线索二叉树
对二叉树以某种次序遍历使其变成线索二叉树的过程叫做线索化
建立线索二叉树的过程就是对二叉树线索化,实质上就是遍历一棵二叉树
所以按照遍历方式的不同分为先序线索,后序线索,中序线索

先序线索

先序线索
让每一个结点存储的前驱和后继信息是按照先序遍历得到的,所以需要做的事
1.遍历所有结点,如果结点的ltag == 0即左孩子存在,则继续向下遍历,
如果ltag ==1,即左孩子不存在,那么要把该节点的前驱填到lchild处,即让lchild指向当前结点的前驱,所以我们在遍历树是要用两个指针,一个指向当前结点curP,和一个指向上一个访问过的结点Pre.右孩子同理
需要注意的是对于同一个结点他的左右空链域的填写时间不同,即不是在填写完lchild后就填rchild,因为对于先序遍历时,一定是遍历到子树的最左下结点的lchild(此时lchild==NULL)时才返回,然后访问右子树,所以当遍历到NULL时,curP指向NULL,Pre指向最左下结点,现在要填写最左下结点的右空链域时即最左下结点的后继,就要把Pre->rchild = cur;(因为最左下结点是Pre,curP是最左下结点的右子树的根节点)有些难懂,还是画图吧!
这里写图片描述

template <class T>
void PreThreading<T>::PreThread(BTNode<T> *bt,BTNode<T> **pre){
    /*
       先序线索是把树中所有空指针域填上该节点的前驱和后继信息(前驱和后继信息按照遍历方式生成)
    也就是说,按照先序遍历的方式遍历每一个节点,如果当前节点的左(右)孩子不存在就应该让本应该
    指向左(右)孩子的指针域指向当前节点的前驱(后继),这样每一个节点都还有其对应的前驱和后继的
    信息,之后在想访问一个结点的前驱和后继信息是就不用在遍历整棵树了,直接查找该节点的信息即可   */

    /*
     对于先序遍历,是先遍历左子树一直到最左下的节点,然后在遍历最左下边的右子树,一次向根节点回退,
     所以在填写空链域的时候是先填写lchild == NULL的结点的前驱信息,此时指针仍需向下遍历判断是否还可以继续
     遍历,所以此时不能填写当前节点的右空链域信息,当遍历到NULL时,开始回退遍历上一个结点的右子树,
     对于最左下的节点来说就是curP指向NULL,Pre指向最左下的节点,此时无法继续向左遍历,递归回退到Pre的右子树
     此时curP指向最左下的节点的右子树的根节点,Pre指向最左下的节点,所以对于最左下的节点的rchild的信息由Pre来
     更改(Pre就是最左下的节点)
     也就是说对于同一个节点,其前驱和后继的信息不是在同一次遍历是确定的
     */

    //bt代表当前节点,pre代表上次访问的结点
    //pre必须是指针的指针,因为pre是形参,在参数列表中声明的没有分配空间
    //所以用pre给变量赋值时有可能会出错(段错误),
    //要想pre所指空间得到分配,必须让分配的空间的地址是指针pre的地址(用赋值实现)
    BTNode<T> *p,*q;
    if(bt != NULL) {
        p = bt->lchild;
        q = bt->rchild;

        if((*pre != NULL) && (p == NULL)) {
            //lflag == 1,lchild指向该节点的前驱
            //lflag == 0,lchild指向该节点左子树
            //若当前访问的节点的左指针为空,则将上次访问的节点赋给左指针域,幷置标志域
            bt->lchild = *pre;
            bt->lflag = 1;
        }//endif
        if((*pre != NULL) && ((*pre)->rchild == NULL)) {
            //rflag == 1,rchild指向该节点的后继
            //rflag == 1,rchild指向该节点右子树 
            //若上次访问的节点的右指针为空,

            (*pre)->rchild = bt;//则将访问的节点指针赋值给当前节点的右指针域
            (*pre)->rflag = 1;//幷置标志域为1
        }//endif

        *pre = bt;
        PreThread(p,pre);//访问左子树
        PreThread(q,pre);//访问右子树
    }//endif
}

中序线索

中序线索
中序遍历与先序遍历相似
这里写图片描述这里写图片描述

template <class T>
void InThreading<T>::InThread(BTNode<T> *p,BTNode<T> **h){
    //先遍历到最左下的叶子节点,使其左孩子是最初调用时传进来的q,
    //在回退时将lchild为空的域指向刚才访问过的节点即该节点的前驱(这种节点p一般是右子树,h是子树根节点或者同根节点的最左叶结点,因为中序遍历最先遍历左子树,回退时才遍历右子树的最左下结点)
    //将上次的节点的rchild指向当前访问的结点,此时p一般是h的右子树最左叶节点

    //填空链域都发生在递归回退时,由于中序遍历的顺序,当前节点的rchild为空时,使rchild指向当前节点的父节点,如果当前节点lchild为空,则其lchild指向最初遍历有子树的那个节点
    if(p) {
        InThread(p->lchild,h);
        //若上次访问节点的右指针为空,则将当前访问的结点指针赋值给其右指针,并置标志位为1
        if((*h != NULL) && ((*h)->rchild == NULL)) {
            (*h)->rchild = p;
            (*h)->rflag = 1;
        }//endif
        //若当前访问的节点的左指针为空,则将上次访问的结点赋值给当前结点的左指针域
        if(p->lchild == NULL) {
            p->lchild = (*h);
            p->lflag = 1;
        }//endif
        *h = p;
        InThread(p->rchild,h);
    }//endif
}

后序线索

后序线索
后序线索这里使用三叉链表存储

*pre*lchildltagdatartag*rchild

这里写图片描述这里写图片描述

template <class T>
static int Thread(TTTNode<T>*p,TTTNode<T>**h){
    //h一般是p的右兄弟或者左孩子,h在函数调用是值为null,
    //填空链域是从深度最深的右叶结点或左叶结点(此时没有右叶结点)
    if(p) {
        Thread(p->lchild,h);
        Thread(p->rchild,h);
        //若上次访问的结点右指针为空,则将当前访问的结点序号填入,并置右标志域为1
        if(*h != NULL && (*h)->rchild == NULL) {
            (*h)->rchild = p;
            (*h)->rflag  = 1;
        }//endif
        //若上次访问的结点左指针为空,则将当前访问的结点序号填入,并置左标志域为1
        if(*h != NULL && p->lchild == NULL) {
            p->lchild = (*h);
            p->lflag  = 1;
        }//endif
        *h = p;//记住当前访问的结点
    }//endif
    return 0;
}

没有看明白可以看看这个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

siyan985

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值