线索二叉树(三种方式线索化+每种找前驱+每种找后继)

目录

先创建线索树

先序

中序

后序


先创建线索树

//14、线索二叉树
typedef struct ThreadBiTreeNode{
    int data;
    struct ThreadBiTreeNode *lchild,*rchild,*parent;//parent域指向双亲结点
    int ltag,rtag;
    int weight;//如果给叶子节点赋权值的话会用到
}ThreadBiTreeNode,*ThreadBiTree;

//先创建树
ThreadBiTree CreateThreadBiTree(){
    ThreadBiTree T = NULL;
    int data;
    scanf("%d",&data);
    if(data != 99999){
        T = (ThreadBiTree)malloc(sizeof(ThreadBiTreeNode));
        T -> ltag = 0;
        T -> rtag = 0;
        T -> data = data;
        T -> lchild = CreateThreadBiTree();
        if(T -> lchild != NULL)
            T -> lchild -> parent = T;
        T -> rchild = CreateThreadBiTree();
        if(T -> rchild!= NULL)
            T -> rchild -> parent = T;
        //如果要给线索二叉树的叶子节点赋个权值的话:
        if(T -> lchild == NULL && T -> rchild == NULL){
            scanf("%d",&data);
            T -> weight = data;
        }
    }
    return T;
}

/**
 * 注①:线索二叉树这边的代码,可以结合着具体序列思考,
 * 比如一棵二叉树,你把他的先序序列先写出来,这个序列可以直观的反映出每一个节点的前驱和后继,
 * 要判断能不能标记,还要去这个真正的树里面判断,
 * 以某一个节点为例,如果这个节点p在序列中是有前驱的,那么判断它在树中有没有左孩子,如果没有就可以用lchild指向前驱pre,
 * 同时也要判断这个前驱pre有没有rchild,如果没有就可以用rchild来标记后继p,
 * 这是一个结合着具体序列和这棵树一起看的过程。
 * 这样完成了一小轮之后,pre和p同时向后移动,看似只完成了p的lchild部分和pre的rchild部分,
 * 但实质上在移动的过程中对每一个节点的lchild和rchild都通过pre和p指针的移动进行了判断完成。
 * key:维持pre和p的关系,通过pre和p尽可能的进行标记,如果p指向的lchild和rchild不为空则标记不了,就向后接着走,
 * 相当于p是负责遍历的,pre是p的前驱。刚开始p指向根,pre为NULL。
 * 注②:爱的魔力转圈圈问题在先序有,在中序和后序中是没有的。
 * 注③:虽然做了线索,但是不是所有的节点都能直接找到他们的前驱和后继的。
 * 注④:先序找前驱和后序找后继类似。他俩寻找的节点不叫最右下或者最左下的节点,而是靠着右或左一直找到的叶子节点。
 */

先序

//①.1先序线索化
void PreThread(ThreadBiTree &p,ThreadBiTree &pre){
    if(p){
        //Visit(p);
        if(p -> lchild == NULL){
            p -> lchild = pre;
            p -> ltag = 1;
        }
        if(pre != NULL && pre -> rchild == NULL){
            pre -> rchild = p;
            pre -> rtag = 1;
        }
        //如果上面if的两种情况都不满足,
        //就说明p的lchild有孩子,就不能标记到pre,也说明pre的rchild有孩子,也不能标记到p,
        //所以本小轮的线索标记就结束了,让pre = p 就进行下一轮了。
        pre = p;//一小轮的线索标记过程结束,为下一轮做准备,让 pre = p。
//      PreThread(p -> lchild,pre);
        //把p->lchild传进去,p变成了原来的p的->的lchild,pre还是原来的那个p,所以pre还是p的前驱,关系还是维持了
        //但是这边其实是有问题的,现在pre和p都指向同一个点,(爱的魔力转圈圈)
        //假如p原来是没有lchild,那么进行上一小轮的线索标记之后,p的lchild就已经被修改过了,
        //并指向了它的前驱(不是现在的pre,是之前的pre,现在pre已经执行过 pre = p了,应该是还没有执行这句的pre),
        //在我们之前学习的PreOrder中,p没有lchild的话就是NULL,就会直接结束运行此函数并返回T出栈了,这一小树就会遍历结束,
        //而这边没有使这个函数停止的条件,lchid又变成了他的前一个,就会形成一个循环,和上一小轮一样,这样一直执行下去,无法停止。
        //那么怎么才能解决这个问题呢?
        //只要搞明白这个传入的参数p->lchild中的p有无被线索化标记过就可以了,如果标记过,说明它原本是没有左孩子的,
        //那么这个lchild其实是p的前驱,就是相当于遇到了PreOrder中的出栈情况,
        //如果p->lchild中的p没有被线索标记过,就说明它是有左孩子的,所以传入的p->lchild确确实实就是p的左孩子。
        //以上两种情况通过tag进行区分,下面是判断条件,应该执行完判断条件之后再PreThread(p -> lchild,pre);:
        if(p -> ltag == 0)
            PreThread(p -> lchild,pre);
        PreThread(p -> rchild,pre);
    }
}

//①.2先序线索树找先序的前驱(可能会借助栈)
ThreadBiTree FindPrepre(ThreadBiTree T){
    if(T -> ltag == 1){//ltag = 1 说明前驱已经标记好了,直接return
        return T -> lchild;
    }else{//ltag = 0 说明有左孩子,但是先序的前驱不可能在左孩子中,只能去他的祖先里面找,要找父亲
        ThreadBiTree parent = T -> parent;
        if(parent -> lchild == T){//如果父亲有左孩子,那么左孩子就是T,父亲就是T的前驱
            return parent;
        }else{
            if(parent -> ltag == 1)//说明父亲节点没有左孩子,那么T就是右孩子,这种情况父亲也是T的前驱
                return parent;
            else{//说明父亲有左孩子,且T是父亲的右孩子,那么要找到左子树进行先序遍历的最后一个节点
                //也就是靠着右找找到叶子节点,也就是ltag和rtag都为0
                ThreadBiTree p = parent -> lchild;//获取一下父亲节点左子树的根结点指针,下面就要开始找先序遍历的最后一个节点了
                while(p -> ltag == 0 || p -> rtag == 0){//说明不是叶子节点,要去找叶子节点
                    while(p -> rtag == 0)
                        p = p -> rchild;//一直靠着右走
                    if(p -> ltag == 0)
                        p = p -> lchild;//往左只走一下,如果有右孩子的话,还是接着往右走,这样整体看起来就是贴着右边走的
                }
                return p;//p就是T的父亲的左子树先序遍历的最后一个节点,也就是p的前驱
            }
        }
    }
}

//①.3先序线索树找先序的后继
ThreadBiTree FinPrepost(ThreadBiTree T){
    if(T -> rtag == 1){//有后继线索直接return
        return T -> rchild;
    }else{
        if(T -> ltag == 1)//说明没有左孩子
            return T -> rchild;
        else//说明有左孩子
            return T -> lchild;
    }
}

中序

//②.1中序线索化
void InThread(ThreadBiTree &p,ThreadBiTree &pre){
    if(p){
//        if(p -> ltag == 0)中序中没有爱的魔力转圈圈问题
        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;
        //上面到此
//        if(p -> rtag == 0)中序中没有爱的魔力转圈圈问题
        InThread(p -> rchild,pre);
    }
}

//②.2中序线索树找中序的前驱
ThreadBiTree FindInpre(ThreadBiTree T){
    if(T -> ltag == 1)//有前驱线索直接return
        return T -> lchild;
    else{
        //找左子树中序遍历的最后一个节点,这个节点一定是偏右的,且没有右孩子!是左子树中最后下的节点,
        //这里和先序找前驱有点区别,这里不是要找到叶子节点,就算找到的这个节点有左孩子也不妨碍它是中序遍历中遍历的最后一个节点。
        ThreadBiTree p = T -> lchild;
        while(p -> rtag == 0)
            p = p -> rchild;//一直向右走直到找到右孩子为空就找到了
        return p;
    }
}

//②.3中序线索树找中序的后继
ThreadBiTree Inpost(ThreadBiTree T){
    if(T -> rtag == 1)//有后继线索直接return
        return T -> rchild;
    else{
        //找右子树中序遍历的第一个节点,这个点一定是偏左的,且没有左孩子!也就是右子树中最左下的节点,和中序找前驱中的类似。
        //找到的这个节点可以有右也可以没有右,不妨碍这个点成为第一个遍历的节点。
        ThreadBiTree p = T -> rchild;//先拿到右子树的根
        while(p -> ltag == 0)
            p = p -> lchild;
        return p;
    }
}

后序

//③.1后序线索化
void PostThread(ThreadBiTree &p,ThreadBiTree &pre){
    if(p){
        PostThread(p -> lchild,pre);
        PostThread(p -> 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;
        //上面到此
    }
}

//③.2后序线索树找后序的前驱
ThreadBiTree Postpre(ThreadBiTree T){
    if(T -> ltag == 1)//有前驱线索直接return
        return T -> lchild;
    else{
        if(T -> rtag == 0)//有右孩子的话,前驱就是右孩子
            return T -> rchild;
        else//没有右孩子的话前驱才是左孩子
            return T -> lchild;
    }
}

//③.3后序线索树找后序的后继(可能会借助栈)
ThreadBiTree Postpost(ThreadBiTree T){
    if(T -> rtag == 1)//有后驱线索直接return
        return T -> rchild;
    else{
        ThreadBiTree parent = T -> parent;
        if(parent -> rchild == T)//如果T是父亲节点的右孩子,那么T的后继就是父亲节点;
            return parent;
        else{
            if(parent -> rtag == 1)//如果T是父亲节点的左孩子,且父亲节点没有右孩子,那么T的后继就是父亲节点;
                return parent;
            else{//如果T是父亲节点的左孩子,且父亲节点有右孩子,那么找右孩子所在子树中后序遍历的第一个节点。(这里和先序找前驱类似)
                //找右孩子所在子树中后序遍历的第一个节点,也就是要找父亲节点的右孩子子树中靠着左找到的第一个叶子节点
                ThreadBiTree p = parent -> rchild;
                while(p -> rtag == 0 || p -> ltag == 0){
                    while(p -> ltag == 0)
                        p = p -> lchild;
                    if(p -> rtag == 0)//往右只走一下,如果有左孩子的话,还是接着往左走,这样整体看起来就是贴着左边走的
                        p = p -> rchild;
                }
                return p;
            }
        }
    }
}

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值