深入学习二叉树(二) 线索二叉树

1 前言

在上一篇简单二叉树的学习中,初步介绍了二叉树的一些基础知识,本篇文章将重点介绍二叉树的一种变形——线索二叉树。

2 线索二叉树

2.1 产生背景

现有一棵结点数目为n的二叉树,采用二叉链表的形式存储。对于每个结点均有指向左右孩子的两个指针域,而结点为n的二叉树一共有n-1条有效分支路径。那么,则二叉链表中存在2n-(n-1)=n+1个空指针域。那么,这些空指针造成了空间浪费。
例如:图2.1所示一棵二叉树一共有10个结点,空指针^有11个。


此外,当对二叉树进行中序遍历时可以得到二叉树的中序序列。例如:图2.1所示二叉树的中序遍历结果为HDIBJEAFCG,可以得知A的前驱结点为E,后继结点为F。但是,这种关系的获得是建立在完成遍历后得到的,那么可不可以在建立二叉树时就记录下前驱后继的关系呢,那么在后续寻找前驱结点和后继结点时将大大提升效率。

2.2 线索化

现将某结点的空指针域指向该结点的前驱后继,定义规则如下:

若结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
若结点的右子树为空,则该结点的右孩子指针指向其后继结点。

这种指向前驱和后继的指针称为线索。将一棵普通二叉树以某种次序遍历,并添加线索的过程称为线索化。
按照规则将图2.1所示二叉树线索化后如图2.2所示:

图中黑色点画线为指向后继的线索,紫色虚线为指向前驱的线索。
可以看出通过线索化,既解决了空间浪费问题,又解决了前驱后继的记录问题。

2.3 线索化带来新问题

经过2.2节讲解后,可以将一棵二叉树线索化为一棵线索二叉树,那么新的问题产生了。我们如何区分一个结点的lchild指针是指向左孩子还是前驱结点呢?例如:对于图2.2所示的结点E,如何区分其lchild的指向的结点J是其左孩子还是前驱结点呢?
为了解决这一问题,现需要添加标志位ltag,rtag。并定义规则如下:

ltag为0时,指向左孩子,为1时指向前驱
rtag为0时,指向右孩子,为1时指向后继

添加ltag和rtag属性后的结点结构如下:

图2.2所示线索二叉树转变为图2.3所示的二叉树。

2.4 线索二叉树结点数据结构

//#define Link 0//指针标志
//#define Thread 1//线索标志
typedef char TElemType;
//中序线索二叉树
typedef enum PointerTag {Link, Thread};//结点的child域类型,link表示是指针,指向孩子结点,thread表示是线索,指示前驱或后继结点
//定义结点数据结构
typedef struct ThrBiNode{  TElemType data;  ThrBiNode *lchild, *rchild;//左右孩子指针  PointerTag lTag, rTag;//左右标志
}ThrBiNode, *ThrBiTree;

2.5 中序遍历建立线索二叉树

中序遍历的方法已经在第一篇二叉树基础中讲解过,那么实现线索化的过程就是在中序遍历同时修改结点空指针的指向。
采用中序遍历的访问顺序实现一棵二叉树的线索化过程代码如下:

//中序遍历进行中序线索化
void inThreading(ThrBiTree T, ThrBiTree &pre){  if(T){  inThreading(T->lchild, pre);//左子树线索化  if(!T->lchild){//当前结点的左孩子为空  T->lTag = Thread;  T->lchild = pre;  }else{  T->lTag = Link;  }  if(!pre->rchild){//前驱结点的右孩子为空  pre->rTag = Thread;  pre->rchild = T;  }else{  pre->rTag = Link;  }  pre = T;          inThreading(T->rchild, pre);//右子树线索化  }
}

2.6 加上头结点,遍历线索二叉树

加上线索的二叉树结构是一个双向链表结构,为了便于遍历线索二叉树,我们为其添加一个头结点,头结点左孩子指向原二叉树的根结点,右孩子指针指向中序遍历的最后一个结点。同时,将第一个结点左孩子指针指向头结点,最后一个结点的右孩子指针指向头结点。
图2.3所示线索二叉树添加头结点后如图2.4所示:


带有头结点的线索二叉树遍历代码如下:

//T指向头结点,头结点的lchild链域指针指向二叉树的根结点
//中序遍历打印二叉线索树T(非递归算法)
void inOrderTraversePrint(ThrBiTree T){  ThrBiNode *p = T->lchild;//p指向根结点  while(p != T){//空树或遍历结束时,p == T  while(p->lTag == Link){  p = p->lchild;  }  //此时p指向中序遍历序列的第一个结点(最左下的结点)  printf("%c ", p->data);//打印(访问)其左子树为空的结点  while(p->rTag == Thread && p->rchild != T){  p = p->rchild;  printf("%c ", p->data);//访问后继结点  }  //当p所指结点的rchild指向的是孩子结点而不是线索时,p的后继应该是其右子树的最左下的结点,即遍历其右子树时访问的第一个节点  p = p->rchild;  }  printf("\n");
}

3 结语

线索二叉树充分利用了指针空间,同时又便于寻找结点的前驱结点和后继结点。线索二叉树适用于经常需要遍历寻找结点前驱或者后继结点的二叉树。

深入学习二叉树(二) 线索二叉树相关推荐

  1. 8.遍历二叉树、线索二叉树、森林

    思考 一.什么遍历二叉树.线索二叉树.森林?(What) 0.二叉树遍历一共(4种) 1.二叉树先序遍历 2.二叉树中序遍历 3.二叉树后序遍历 4.遍历分析 5.遍历二叉树 6.二叉树层次遍历 7. ...

  2. 线索二叉树,画图教你秒懂线索二叉树(线索二叉树的建立和简单操作)逻辑代码分析

    数据结构专升本学习,线索二叉树 前言 前面我们学习树和二叉树的一些基本操作,今天我们学习一个新的知识,学习一下线索二叉树,线索二叉树是由二叉链存储结构变化而来的(我们先得有个二叉链树,再做处理),就是 ...

  3. 重拾算法(3)——用458329个测试用例全面测试二叉树和线索二叉树的遍历算法

    重拾算法(3)--用458329个测试用例全面测试二叉树和线索二叉树的遍历算法 在"上一篇"和"上上一篇"中,我给出了二叉树和线索二叉树的遍历算法.给出算法容易 ...

  4. 【Java数据结构与算法】第十一章 顺序存储二叉树、线索二叉树和堆

    第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 文章目录 第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 一.顺序存储二叉树 1.介绍 2.代码实现 二.线索二叉树 1 ...

  5. 数据结构入门----遍历二叉树和线索二叉树

    遍历二叉树(Traversing Binary Tree) 二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次. 实际应用中,查找树中符合条件的结点 ...

  6. c语言线索二叉树作用,线索二叉树(C语言)

    实现下面这棵树: 先序遍历: A B C D E F 中序遍历: C B D A E F 代码 #include #include #include #include typedef enum {li ...

  7. 【练习】2021下半年数据结构刷题笔记和总结 (二) 树、查找-- 不同的排序算法、二叉排序树 平衡二叉树、哈希表查找、线索二叉树、

    记录自己下半年写题目的记录.题目来自书或者网站. 练习(一)的地址: https://blog.csdn.net/qq_41358574/article/details/117098620?ops_r ...

  8. 数据结构与算法(八)-二叉树(斜二叉树、满二叉树、完全二叉树、线索二叉树)...

    前言:前面了解了树的概念和基本的存储结构类型及树的分类,而在树中应用最广泛的种类是二叉树 一.简介 在树型结构中,如果每个父节点只有两个子节点,那么这样的树被称为二叉树(Binary tree).其中 ...

  9. 数据结构与算法学习笔记-树和二叉树

    声明:本博客仅为本人学习途中做的笔记 采自青岛大学王卓老师的视频教学 主要内容为算法思路,具体代码实现还需修改后才能运行,望各位看官多多包涵,您的点赞与评论是对我最大的肯定! 1.树和二叉树的定义 数 ...

最新文章

  1. 在Windows7/10上通过VS2013编译FFmpeg 4.1.3源码操作步骤
  2. 17.电话号码的组合
  3. v7000更换电池步骤_v7000 gen1 电池更换问题
  4. mysql 对多列进行排序 分组,尝试从多列全文mysql搜索中对结果进行排序
  5. ios15 LJScrollPageVC第三方框架的使用
  6. C#编程尽量使用接口(转)
  7. 矩阵 计算机应用,《计算机视觉算法:基于OpenCV的计算机应用开发》 —3.3 元素级矩阵操作...
  8. android+多种行视图,android – 在react-native列表视图中显示多种类型行的正确方法是什么?...
  9. linux 多核线程 调度,通过轻型线程提高多核设备中的Linux实时性能
  10. 获取会话名称时错误 5_2019Java面试宝典系列|基础篇5
  11. OAuth 授权的工作原理是怎样的?
  12. 使用浏览器地址栏调用CXF Webservice的写法
  13. Hadoop平台搭建
  14. qq 实现连接内网_如何穿透内网稳定进行远程桌面?花生壳盒子来搞定
  15. 1.12 Linux查看用户信息
  16. 音乐网站Spotify将融资4亿美元,估值84亿美元
  17. CREO图文教程:三维设计案例之风扇叶制作图文教程之详细攻略
  18. 订阅消息 data.thing1.value is emtpy
  19. 深度学习分类类别不平衡_Github|类别不平衡学习资源(上)
  20. Html设置表格撑开,【CSS】表格或div被撑开的解决办法

热门文章

  1. [转载] 1022 D进制的A+B (20分)【java题解】【80ms】
  2. oracle中dbms_并发和由于DBMS中的并发导致的问题
  3. https 密钥 php,https加密方式是什么
  4. python requests和urllib_Python——深入理解urllib、urllib2及requests(requests不建议使用?)...
  5. python中bool函数的用法_python3实战python函数每日一讲 - bool([x])
  6. c语言long的格式字符串,时间字符串和long类型之间的转换
  7. 如何手撸一个队列?队列详解和面试题汇总(含答案)
  8. C#中其他简单LINQ查询表达式的简单使用介绍
  9. cad画直角命令_炸了,CAD fro命令配合tk命令,极轴追踪无敌!
  10. IDEA配置xml文件头报错:URI is not registered (Settings | Languages Frameworks | Schemas and DTDs)解决方法,亲测有效