数据结构难点——线索二叉树的详解


注:
在阅读这篇文章之前,建议读者先掌握递归的概念和原理,以便更好地理解和欣赏文章中的内容。

线索二叉树的出现

普通二叉树的查询元素都必须从根结点开始,从头开始遍历,但是如果只对二叉树元素中一个非根结点的元素,要想查找其前驱结点和后继结点是十分不方便的(虽然可以实现,采用双指针的方式即可,但是麻烦且算法效率低)

于是人们研究出了线索二叉树来解决这个问题

定义二叉树时,不管是先序遍历,中序遍历还是后续遍历,都存在大量的空指针,于是利用这些空指针,将其指向元素的前驱结点和后驱结点(两者存在的时候),就可以建立线索二叉树。

本文章将以中序二叉树为例子来,重点解释线索二叉树的作用和与普通二叉树的不同之处

中序二叉树

请添加图片描述

中遍历结果为:D B E A F C G

借助中序遍历后的结果,可以让我们更直观的感受到元素的前驱结点和后继结点

不使用中序线索二叉树

不使用中序线索二叉树的思路为:

  1. 从根结点出发,重新进行一次中序遍历,但是这次用两个指针q,pre来记录
  2. 指针q记录当前访问的结点,指针pre记录上一次被访问的结点(即q比pre多走一步)
  3. 当q指针访问的结点是我们的目标结点时,结束,此时pre就是其前驱结点
  4. 同时,如果要确定其后继结点时,则在让pre和q继续运行一次,让pre = 目标结点,这是q就是目标结点的后继指针

结论:

  1. 使用两个指针p,pre,进行中序遍历
  2. 当q == 目标结点 时,pre为前驱结点
  3. 当 pre == 目标结点 时,q为后继结点

这里用两个指针进行中序遍历,明显增加了程序的运行时间,并且使用起来也比较繁琐。

使用中序线索二叉树(难点)

中序二叉树就是将结点的内容扩大,之前的结点只存储指向其双亲结点的指针,现在扩充其范围,添加指向前驱和后继的两个指针和判定是否存在前驱或后继结点的判断区域
请添加图片描述
其中

  • *lchild *rchild存放其前驱和后继结点
  • ltag rtag判断是否存在前驱和后继结点,用1,0来表示是否存在

图示:
请添加图片描述
请添加图片描述
注:读者在阅读下面思路的时候,可以边画图边理解,这样更方便理解。
思路(结合下面的代码层面仔细阅读,理解是如何遍历二叉树的,是如何建立线索从而构建中序二叉树的):

  1. 用p指针遍历左子树的时候,会一直访问到D结点,因为D结点没有左孩子,于是将D结点的左孩子赋值为pre(此时pre为空),然后将pre变成D,再执行InThread(p->rchild,pre);
  2. 此时p为结点G,但是pre还是结点D,这时候执行代码p->lchild = pre;,G的左孩子(前驱结点)指向D结点,再执行pre->rchild = p,D的右孩子(后继结点)指向G结点,同时,这一躺遍历结束,pre指向G;
  3. 将pre给到G结点之后,从A->B->D->G这一趟的遍历结束;
  4. 由于递归是采用栈的形式进行的,这时候,将G,D出栈,开始对B结点进行操作
  5. 注意,这是给到结点B时,不需要再进行InThread(p->lchild,pre);这一步,因为遍历左边已经结束,出栈给到了B,直接开始执行后面的代码。
  6. 此时执行if(!pre->rchild)语句段,这时候,将G结点的右指针给到了B,此时G结点已完成线索的搭建,然后将pre给到B结点,然后p给到A结点。
  7. 此时pre为B结点,执行if(!pre->rchild)语句段,将B的右孩子给到A结点,此时,左子树已经完成了所有的线索搭建
  8. 右子树和左子树一样,结合递归就可以理解。

代码示例:

//完整过程
//线索二叉树的结构体
typedef struct node
{
    ElemType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;      //记录是否存在前驱,后继结点
} ThreadNode;

//创建
void CreatInThreed(ThreadNode *T)
{
    ThreadNode *pre = NULL;
    if(T != NULL)
    {
        InThread(T,pre);
        pre->rchild = NULL;
        pre->rtag = 1;
    }
}

//线索化二叉树
void InThread(ThreadNode *p,ThreadNode *pre)
{
    if(p)
    {
        InThread(p->lchild,pre);//递归遍历,直到左孩子不存在时递归完毕
        if(!p->lchild)      //p->child为空时执行
        {
            p->lchild = pre;
            p->ltag = 1;
        }
        if(!pre->rchild)    //pre->child为空时执行
        {
            pre->rchild = p;    //将前驱结点的后继指针指给p
            pre->rtag = 1;
        }
        pre = p;
        InThread(p->rchild,pre);
    }
}

最终图示:
请添加图片描述

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值