c语言后序线索二叉树(超详细)学习笔记

前言:

本文章是继 上一篇我的文章 C语言中序线索二叉树(超详细)学习笔记 续写的后序顺序线索二叉树,若是不懂中序,想学后序,建议先看懂中序。

我的文章有点长,希望你能够耐心看完,一定一定会有所收获的!

一、创建二叉树结构体

因为线索二叉树 需要运用标记来区分是否有左孩子还有右孩子 所以定义 整型ltag和rtag,并规定 ltag= 0  代表有 左孩子,ltag = 1 代表没有左孩子;rtag = 0 代表有右孩子,rtag =  1 代表没有右孩子。在后序线索中,我们需要用到双亲结点,也就是它的父亲结点,所以我们需要再定义父亲结点指针。

typedef struct TreeNode
{
    char data;
    int ltag, rtag;
    struct TreeNode* lChild;
    struct TreeNode* rChild;
    struct TreeNode* parent;
  
}TreeNode;

二、二叉树的初始化

初始化方式

仍是使用先序方式进行传入数据 并运用递归。如果想了解递归详细的推导,可以看看我的这篇文章 c语言二叉树的创建与前序、中序、后序遍历(超详细)学习笔记 

与此不同的是,新增了形参 TreeNode  * parant,目的是为了将自己的父亲结点也记录其中。父亲结点在第五步(五、得到下一个元素函数)里有作用

所以在刚开始调用此函数需填入NULL,即creatTree(NULL),因为根节点没有父亲结点,所以是NULL,之后通过递归依次将每个结点的父亲结点记录其中。

TreeNode* creatTree(TreeNode * parent)
{
    TreeNode* T;
    char ch;
    scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点
    if (ch == '#')
    {
        //为空结点
        return NULL;
    }
    else
    {
       
        T = (TreeNode*)malloc(sizeof(TreeNode));
      
        T->data = ch;
        T->ltag = 0;
        T->rtag = 0;
        T->parent = parent;
        T->lChild = creatTree(T);
        T->rChild = creatTree(T);
        return T;
    }
}

三、后序线索遍历

思路:

代码:

void posThreadTree(TreeNode* T, TreeNode** pre)
{
   
    if (T)//传进来的结点是否为空
    {    
        posThreadTree(T->lChild, pre);//指向最深的左孩子  
        posThreadTree(T->rChild, pre);//指向右孩子    
        //干事情
        if (T->lChild == NULL) //如果左孩子为空 指向前驱       假设当前为树结点 为 A   
        {
            T->ltag = 1;      //标记为1 
            T->lChild = *pre; //A的左孩子指向前驱元素  假设当前 前驱元素为B
        }
        if (*pre != NULL && (*pre)->rChild == NULL)//如果前驱元素B不为空 且 它的右孩子为空 指向后继元素 A
        {
            (*pre)->rtag = 1;//标记为1
            (*pre)->rChild = T; //B的右孩子 指向 后继元素A
        }
        *pre = T;
    }
}

代码解释:

二级指针 是为了在函数体内可以改变指针的地址,即可以通过函数来改变实参的值,指针*pre的值可以随时改变。

先考虑框架 左右根   左: inThreadTree(T->lChild, pre);右: inThreadTree(T->rChild, pre) ;根:干事情  再考虑核心 就是干事情。这一步代码里的注释很详细。假设当前树结点为A,前驱结点为B,前两个if语句都满足的情况下:

然后再执行(*pre)= T.A又成为了前驱

递归思路演示:

递归:分为递归分为两步,第一步到达函数最深层,第二步原路返回到函数第一层,并返回最终返回值。此递归函数为void类型递归,调用递归函数在前,干事情在后,也需要分两步走。详情:可以看我的这篇文章:浅说递归的含义及递归的整个过程

前提:先将*pre = NULL(这步main函数将*pre初始化时就等于NULL)找到最深的左孩子D,T = D;D没有左孩子和右孩子,不会执行 posThreadTree(T->lChild, pre)和posThreadTree(T->rChild, pre);来到第一个if语句判断D没有左孩子,ltag = 1;并将指向左孩子改为指向前驱元素 T->lChild = *pre(解引用操作);所以   NULL<=D第二个if语句*pre == NULL,不满足直接跳过,进行 *pre = T; 将D 变为了 *pre(前驱元素)此时*pre不再为NULL 。完成了最深的左孩子D,该执行B的右孩子E了(递归走完第一步,该走第二步逐步返回上一层直到第一层。遵循左右根原则,B的左孩子D干完了,就干B的右孩子E了)即 posThreadTree(B->rChild, D); T = E;E没有左孩子和右孩子,不会执行 posThreadTree(T->lChild, pre)和posThreadTree(T->rChild, pre);来到第一个if语句判断E没有左孩子,ltag = 1;并将指向左孩子改为指向前驱元素 D;所以   NULL<=D<=E第二个if语句判断D没有右孩子,满足条件,将D指向右孩子改为指向后继元素 E,所以   NULL<=D<= =>E,进行 *pre = T; 将E 变为了 *pre(前驱元素)。完成了B的右孩子了,该执行B自己了(继续走第二步逐步返回上一层直到第一层。遵循左右根原则,B的右孩子E干完了,就干B自己的事情了) 直接开始干事情了,来到第一个if语句判断B有左孩子,不满足条件,直接跳过,第二个if语句判断E没有右孩子,ltag = 1;并将指向右孩子改为指向后继元素 B;所以   NULL<=D<= =>E=>B,进行 *pre = T; 将B 变为了 *pre(前驱元素)该执行A的右孩子C了(继续走第二步逐步返回上一层直到第一层。遵循左右根原则,A的左孩子B干完了,就干A的右孩子C了)即 posThreadTree(A->rChild, C);T = C;C没有左孩子和右孩子,不会执行 posThreadTree(T->lChild, pre)和posThreadTree(T->rChild, pre);来到第一个if语句判断C没有左孩子,ltag = 1;并将指向左孩子改为指向前驱元素 B;所以   NULL<=D<= =>E=>B<=C,第二个if语句判断B有右孩子,不满足直接跳过,进行 *pre = T; 将C 变为了 *pre(前驱元素)。完成了A的右孩子了,该执行A自己了(第二步逐步返回上一层到达第一层。遵循左右根原则,A的右孩子C干完了,就干A自己的事情了)直接开始干事情了,来到第一个if语句判断A有左孩子,不满足条件,直接跳过,第二个if语句判断C没有右孩子,ltag = 1;并将指向右孩子改为指向后继元素 A;所以   NULL<=D<= =>E=>B<=C=>A,进行 *pre = T; 将A 变为了 *pre(前驱元素)函数执行结束。最终效果为: NULL<=D<= =>E=>B<=C=>A

四、得到最深的左孩子函数

做第四第五步是为了最后用for循环将元素一个接着一个打印输出而准备。

再这里还需考虑最深的左孩子还有没有右孩子右孩子还有后代,有的话就继续getFirst(),直到右孩子的最深孩子。

TreeNode* getFirst(TreeNode* T)  //得到最深的左孩子  
{
    while (T->ltag == 0)   
    {
        T = T->lChild;
    }
    if (T->rtag == 0)      
    {   //如果它有右孩子 先把右孩子这颗树返回
        getFirst(T->rChild);
    }   //没有右孩子 就返回自己
    return T;
}

五、得到下一个元素函数

TreeNode* getNext(TreeNode* node)  //线性表
{
    if (node->rtag == 1)    //如果它没有右孩子
    {
        return node->rChild;//就返回它的后继元素
    }
    else                   //如果它有右孩子
    {   //如果是根结点
        if (node->parent == NULL) return NULL;
        //如果是右孩子    node 是 右孩子 已经干完了,该指向它的父亲了
        else if (node->parent->rChild == node) return node->parent;
        //如果是左孩子    node 是 左孩子 已经干完了,该指向同层的右孩子了
        else
        {
            //有右孩子 返回右孩子这棵树      此时 指向它同层的右孩子,开始干右孩子这棵树了
            if(node->parent->rtag == 0) return getFirst(node->parent->rChild);
            //没有右孩子 返回它的父亲
            else
                return node->parent;
        }
    }
}

代码注释的很详细,根据左右根顺序,依次去判断,//有右孩子 返回右孩子这棵树   是因为是因为此时右孩子也是一课树的根,得按照后序顺序,需找出它的最深左孩子再右孩子再根,没有也就返回自己

六、用for循环遍历打印

例如 将 D  E  B  C  A 一个一个输出。  node =最深左孩子D,进行打印,再使node 变成 下一个元素 E ,进行打印...直到node == NULL。

  for (TreeNode* node = getFirst(T); node != NULL; node = getNext(node))
    {
        printf("%c ", node->data);
    }
    printf("\n");

七、完整代码

#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
    char data;
    int ltag, rtag;
    struct TreeNode* lChild;
    struct TreeNode* rChild;
    struct TreeNode* parent;
  
}TreeNode;
//初始化
TreeNode* creatTree(TreeNode * parent)
{
    TreeNode* T;
    char ch;
    scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点
    if (ch == '#')
    {
        //为空结点
        return NULL;
    }
    else
    {
       
        T = (TreeNode*)malloc(sizeof(TreeNode));
      
        T->data = ch;
        T->ltag = 0;
        T->rtag = 0;
        T->parent = parent;
        //printf("");
        T->lChild = creatTree(T);
        T->rChild = creatTree(T);
        return T;
    }
}
void posThreadTree(TreeNode* T, TreeNode** pre)
{
   
    if (T)//传进来的结点是否为空
    {    
        posThreadTree(T->lChild, pre);//指向最深的左孩子  
        posThreadTree(T->rChild, pre);//指向右孩子    

        if (T->lChild == NULL) //如果左孩子为空 指向前驱       假设当前为树结点 为 A   
        {
            T->ltag = 1;      //标记为1 
            T->lChild = *pre; //A的左孩子指向前驱元素  假设当前 前驱元素为B
        }
        if (*pre != NULL && (*pre)->rChild == NULL)//如果前驱元素B不为空 且 它的右孩子为空 指向后继元素 A
        {
            (*pre)->rtag = 1;//标记为1
            (*pre)->rChild = T; //B的右孩子 指向 后继元素A
        }
        *pre = T;
    }
}
TreeNode* getFirst(TreeNode* T)  //得到最深的左孩子  
{
    while (T->ltag == 0)   
    {
        T = T->lChild;
    }
    if (T->rtag == 0)      
    {   //如果它有右孩子 先把右孩子这颗树返回
        getFirst(T->rChild);
    }   //没有右孩子 就返回自己
    return T;
}
TreeNode* getNext(TreeNode* node)  //线性表
{
    if (node->rtag == 1)    //如果它没有右孩子
    {
        return node->rChild;//就返回它的后继元素
    }
    else                   //如果它有右孩子
    {   //如果是根结点
        if (node->parent == NULL) return NULL;
        //如果是右孩子    node 是 右孩子 已经干完了,该指向它的父亲了
        else if (node->parent->rChild == node) return node->parent;
        //如果是左孩子    node 是 左孩子 已经干完了,该指向同层的右孩子了
        else
        {
            //有右孩子 返回右孩子这棵树      此时 指向它同层的右孩子,开始干右孩子这棵树了
            if(node->parent->rtag == 0) return getFirst(node->parent->rChild);
            //没有右孩子 返回它的父亲
            else
                return node->parent;
        }
    }
}
int main()
{
    TreeNode* T = creatTree(NULL);
    TreeNode* pre = NULL;
    posThreadTree(T, &pre);
    for (TreeNode* node = getFirst(T); node != NULL; node = getNext(node))
    {
        printf("%c ", node->data);
    }
    printf("\n");
    return 0;
}

八、运行结果

输入ABD##E##C##

总结:

线索二叉树的好处在于它能够提高二叉树的遍历效率。传统的二叉树遍历方式,如先序遍历、中序遍历、后序遍历,需要通过递归或循环遍历整棵二叉树,而在线索二叉树中,每个节点都含有指向其前驱和后继节点的线索信息,因此可以直接访问每个节点的前驱和后继节点,从而不需要进行递归或循环遍历,大大提高了遍历的效率。

制作不易,真心想让你懂,还是有不足的地方,望见谅嘞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小苏先生.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值