当前位置: 首页 > news >正文

第7章树和二叉树:线索二叉树

7.7 线索二叉树

1. 线索二叉树的基本概念

遍历一棵二叉树,实质上就是将非线性结构进行线性化操作,即可以将二叉树中的结点排列成一个线性序列,对于线性序列中的每个结点,有且只有一个直接前驱和一个直接后继。但是,如果以二叉链表作为二叉树的存储结构,在线性序列中的结点,不能直接得到其前驱和后继,只能在遍历的动态过程中才能得到。

在二叉链表所表示的二叉树中,结点会有空链域,那么可以充分利用这些空链域来存放结点的前驱和后继信息。为此做如下规定:

  • 若结点有左子树,则其 lchild 域指示其左孩子,否则令 lchild 域指示其前驱;
  • 若结点有右子树,则其 rchild 域指示其右孩子,否则令 rchild 域指示其后继。

为了避免混淆,还需改变结点结构,增加两个标志域,其结点形式如图 7.7.1 所示。

在这里插入图片描述

图 7.7.1 线索二叉树的结点形式

其中:
LTag={0lchild域指示结点的左孩子1lchild域指示结点的前驱RTag={0rchild域指示结点的右孩子1rchild域指示结点的后继\begin{split} \text{LTag}=\begin{cases}0~\text{lchild}域指示结点的左孩子\\1~\text{lchild}域指示结点的前驱\end{cases} \\\text{RTag}=\begin{cases}0~\text{rchild}域指示结点的右孩子\\1~\text{rchild}域指示结点的后继\end{cases} \end{split} LTag={0 lchild域指示结点的左孩子1 lchild域指示结点的前驱RTag={0 rchild域指示结点的右孩子1 rchild域指示结点的后继
据此,可以修改二叉链表的结点数据结构为:

typedef struct BiTNode{TElemType data;  //结点数据域struct BiTNode *lchild, *rchild; //左右孩子指针 int LTag, RTag; //左右标志
}BiThrTNode, *BiThrTree;

以这种结点结构构成的二叉链表作为二叉树存储结构,叫做线索链表。

  • 指向结点前驱和后继的指针,叫做线索。
  • 加上线索的二叉树称为线索二叉树(threaded binary tree)。
  • 对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。

2. 构造线索二叉树

由于线索二叉树构造的实质是将二叉链表中的空指针域改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历时才能得到,因此线索化的过程即为在遍历的过程中修改空指针的过程。显然,这个过程可用递归算法。

对二叉树按照不同的遍历次序进行线索化,可以得到不同的线索二叉树:

  • 先序遍历:先序线索二叉树
  • 中序遍历:中序线索二叉树
  • 后序遍历:后序线索二叉树

下面重点介绍中序线索化的算法。

为了记下遍历过程中访问结点的先后关系,附设一个指针 pre 始终指向刚刚访问过的结点,而指针 p 指向当前访问的结点,由此记录下遍历过程中访问结点的先后关系,如图 7.7.2 所示:

  • pre 指向的结点没有右孩子的时候,那么给该结点加上右线索,即 RTag = 1 ,让 pre 的右孩子指针指向后继结点 p
  • p 指向的结点没有左孩子的时候,那么给该结点加上左线索,即 LTag = 1 ,让 p 的左孩子指针指向前驱结点 pre

在这里插入图片描述

图 7.7.2 结点间的先后关系

(1)以树中任意一个结点 p 为根的子树中序线索化的过程

【算法步骤】

  1. 如果 p 非空,左子树递归线索化。
  2. 如果 p 的左孩子为空,则给 p 加上左线索,将其 LTag 置为 1,让 p 的左孩子指针指向 pre(前驱);否则将 pLTag 置为 0。
  3. 如果 pre 的右孩子为空,则给 pre 加上右线索,将其 RTag 置为 1,让 pre 的右孩子指针指向 p (后继);否则将 preRTag 置为 0 。
  4. pre 指向刚访问过的结点 p ,即 pre = p
  5. 右子树递归线索化。

【算法描述】

void InThreading(BiThrTree p){//pre 是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索if(p){InThreading(p->lchild); //左孩子递归线索化if(!p->lchild){//p 的左孩子为空p->LTag = 1; //给 p 加上左线索p->lchild = pre; //p 的左孩子指针指向 pre(前驱)}else{p->LTag = 0; }if(!pre->rchild){//pre 的右孩子为空pre->RTag = 1; //给 pre 加上右线索pre->rchild = p; //pre 的右孩子指针指向 p(后继)}else{p->RTag = 0}pre = p; //保持 pre 指向 p 的前驱InThreading(p->rchild); //右子树递归线索化}
}

(2)完成整个二叉树的中序线索化

将二叉树线索化后,所得到的是本质上是一个链表,为此,也可以仿照链表的存储结构,在二叉树的线索链表上也添加一个头结点,并令:

  • lchild 域的指针指向二叉树的根结点;
  • rchild 域的指针指向中序遍历时访问的最后一个结点;
  • 令二叉树中序序列中第一个结点(最左的结点)的 lchild 域指针和最后一个结点(最右的结点) rchild 域的指针均指向头结点(如图 7.7.3)。

这好比为二叉树建立了一个双向线索链表,既可从第一个结点起顺后继进行遍历,也可从最后一个结点起顺前驱进行遍历。

在这里插入图片描述

图 7.7.3 中序线索链表

带头结点的二叉树中序线索化算法如下:

【算法描述】

void InOrderThreading(BiThrTree &Thrt, BitThrTree T){//中序遍历二叉树 T,并将其中序线索化,Thrt 指向头结点Thrt = new BitThrNode; //建头结点Thrt->LTag = 0; //头结点有左孩子,若树非空,则其左孩子为树根Thrt->RTag = 1; //头结点的右孩子指针为右线索Thrt->rchild = Thrt; //初始化时头结点右孩子指针指向自己if(!T){Thrt->lchild = Thrt; //若树为空,则头结点左孩子指针指向自己} else {Thrt->lchild = T;//头结点的左孩子指向根pre = Thrt; //pre 初始值指向头结点InThreading(T); //对 T 为根的二叉树进行中序线索化//上一行算法结束后,pre 为最右结点,pre 的右线索指向头结点pre->rchild = Thrt; pre->RTag = 1;Thrt->rchild = pre; //头结点的右线索指向 pre}
}

3. 遍历线索二叉树

分 3 种情况讨论在线索二叉树中如何查找结点的前驱和后继。

(1)在中序线索二叉树中查找

以图 7.7.3 为例:

① 查找 p 指针所指结点的前驱:

  • p->LTag 为 1,则 p 的左链指示其前驱;

  • p->LTag 为 0,则说明 p 有左子树,结点的前驱是遍历左子树时最后访问的一个结点(左子树中最右下的结点)。

② 查找 p 指针所指结点的后继:

  • p->RTag 为 1,则 p 的右链指示其后继,以图 7.7.3 所示的中序线索树为例,结点 b 的后继为结点 *
  • p->RTag 为 0,则说明 p 有右子树。根据中序遍历的规律可知,结点的后继应是遍历其右子树时访问的第一个结点,即右子树中最左下的结点。例如在找结点 * 的后继时首先沿右指针找到其右子树的根结点 -,然后顺其左指针往下直至其左标志为 1 的结点,即为结点 * 的后继,在图 7.7.3 中是结点 c

(2)在先序线索二叉树中查找

在这里插入图片描述

图 7.7.4 先序线索链表

① 查找 p 指针所指结点的前驱:

  • p->LTag 为 1,则 p 的左链指示其前驱;
  • p->LTag 为 0,则说明 p 有左子树。此时 p 的前驱有两种情况:
    • *p 是其双亲的左孩子,则其前驱为其双亲结点;
    • 否则应是其双亲的左子树上先序遍历最后访问到的结点。

② 查找 p 指针所指结点的后继:

  • p->RTag 为 1,则 p 的右链指示其后继;
  • p->RTag 为 0,则说明 p 有右子树。按先序遍历的规则可知,*p 的后继必为其左子树根(若存在)或右子树根。

(3)在后序线索二叉树中查找

在这里插入图片描述

图 7.7.5 后序线索链表

① 查找 p 指针所指结点的前驱:

  • p->LTag 为 1,则 p 的左链指示其前驱;
  • p->LTag 为 0,
    • p->RTag 也为 0 时,则 p 的右链指示其前驱;
    • p->RTag 为 1 时,则 p 的左链指示其前驱。

② 查找 p 指针所指结点的后继情况比较复杂,分以下情况讨论:

  • *p 是二叉树的根,则其后继为空;
  • *p 是其双亲的右孩子,则其后继为双亲结点;
  • *p 是其双亲的左孩子,且 *p 没有右兄弟,则其后继为双亲结点;
  • *p 是其双亲的左孩子,且 *p 有右兄弟,则其后继为双亲的右子树上按后序遍历列出的第一个结点(即右子树中“最左下”的叶结点)。

由于有了结点的前驱和后继信息,线索二叉树的遍历操作无需使用栈,避免了频繁的进栈、出栈,因此在时间和空间上都较便利二叉树节省。

如果遍历某种次序的线索二叉树,则只要从该次序下的根结点出发,反复查找在该次序下的后继,直到叶子结点。

以遍历中序线索二叉树为例:

【算法步骤】

  1. 指针 p 指向根结点。

  2. p 为非空树或遍历未结束时,循环执行以下操作:

    • 沿左孩子向下,到达最左下结点 *p,它是中序的第一个结点;

    • 访问 *p

    • 沿右线索反复查找当前结点 *p 的后继结点并访问后继结点,直至右线索为 0 或者遍历结束;

    • 转向 p 的右子树。

【算法描述】

void InOrderTraverse_Thr(BiThrTree T){//T 指向头结点,头结点的左链 lchild 指向根结点//中序遍历二叉线索树 T 的非递归算法,对每个数据元素直接输出p = T->lchild; //p 指向根结点while(p != T){//空树活遍历结束时,p==Twhile(p->LTag == 0){p = p->lchild; //沿左孩子向下}cout << p->data; //访问其左子树为空的结点while(p->RTag == 1 && p->rchild != T){p = p->rchild; //沿右线索访问后继结点count << p->data;}p = p->rchild; //转向 p 的右子树}
}

【算法分析】

  • 时间复杂度 O(n)O(n)O(n)
  • 空间复杂度 O(1)O(1)O(1),线索二叉树的遍历不需要使用栈来实现递归操作。

例 7.7.1 判断线索二叉树中 *p 结点有右孩子的条件是( )。

A. p != NULL\qquad B. p->rchild != NULL\qquad C. p->rtag == 0\qquad D. p->tag == 1

【解】

线索二叉树中用 rtag 标识结点的右指针是否为线索。

本题答案:C

例 7.7.2 若 x 是中序线索二叉树中一个有左孩子的结点,且 x 不为根结点,则 x 的前趋结点为( )

A. x 的双亲结点

B. x 的右子树中最左下结点

C. x 的左子树中最右下结点

D. x 的左子树中最右下结点

【解】

设 x 的左孩子为 y,则 x 的前趋结点为以 y 为根结点的最后一个结点,而一棵二叉树的中序序列最后一个结点为其最右下结点。

本题答案:C

http://www.dtcms.com/a/590338.html

相关文章:

  • 网站专题制作流程深圳建网站哪个好
  • java的tomcat源码的http的session
  • 黑河北京网站建设网站架构计划书
  • 局域网网站建设软件怎么自己开公司
  • 网站建设是系统工程广州网站建设 名片制作 网站管理
  • 静态网站做淘宝客如何做英文网站的外链
  • 企业二级网站怎么做建邺做网站价格
  • 【Envi遥感图像处理】018:envi编辑头文件的方法及具体应用
  • RHCE复习作业2
  • AUTOACT论文翻译
  • html5 做手机网站什么是做学院网站
  • ipad 建网站电商入门教学
  • e2ee网站开发框架2.23先行版wordpress小论坛小程序
  • 携程网建设网站的理由济南智能网站建设报价
  • flash网站在线diy源码如何做网站首页关键词
  • B树与B+树核心差异深度解析
  • 11.9 脚本调试 手机网页调试参考
  • 制作网站公司网址开发公司公司简介
  • 满洲里做网站郑州企业网站价格
  • FoundationPose:统一的新物体6D姿态估计与跟踪
  • 地方志网站建设方案南京平台网站建设
  • 杭州建站模板黄岛网站建设公司
  • C语言容易被忽略的易错点(2)
  • 十堰网站建设u2028深圳住房与建设网站
  • 栖霞建设招标网站包装设计网课答案
  • 网站建设引入谷歌地图淘宝客网站虚拟主机
  • [题解]龟兔赛跑 | PTA
  • 禁闭求生2/Grounded 2|网络联机|30GB|简体中文|支持多种操作设备
  • 网站开发流行语言php网站开发实训指导书
  • 百科类网站建设静态网站开发课程相关新闻