数据结构篇——线索二叉树

devtools/2025/3/26 4:07:25/

一、引入


        遍历二叉树是按一定规则将二叉树结点排成线性序列,得到先序、中序或后序序列,本质是对非线性结构线性化,使结点(除首尾)在线性序列中有唯一前驱和后继;但以二叉链表作存储结构时,只能获取结点左右孩子信息,无法直接得任一序列中的前驱和后继信息,该信息需在遍历动态过程中获取,所以我们将引入线索二叉树来保存遍历动态过程中得到的前驱和后继信息。

二、线索二叉树的基本概念 


        试做如下规定:若结点有左子树,则其lchild域指示其左孩子,否则令 Ichild域指示其前驱;若结点有右子树,则其rchild 域指示其右孩子,否则令rchild域指示其后继。为了避免混淆,尚需改变结点结构,增加两个标志域,其结点形式如图所示。

其中:LTag=0,lchild指示结点的左孩子;=1时,lchild指示结点的前驱。

          RTag=0,rchild指示结点的右孩子;=1时,rchild指示结点的后继。

        这样我们就能得到二叉树的二叉线索类型定义如下:

//二叉树的二叉线索存储表示
typedef struct BiThrNode{TElemType data;struct BiThrNode *lchild,*rchild;    //左右孩子的指针int LTag,RTag;    //左右标志
}BiTheNode,*BiThrThree;

        以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树(Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化
        例如下图中(a)所示为中序线索二叉树,与其对应的中序线索链表如图5.16(b)所示。其中实线为指针(指向左、右子树),虚线为线索(指向前驱和后继)。

三、线索二叉树的构造与遍历 


3.1、构造线索二叉树


         线索二叉树构造的实质是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历时才能得到,因此线索化的过程即为在遍历的过程中修改空指针的过程。
        Tips:为了记下遍历过程中访问结点的先后关系,附设一个指针pre 始终指向刚刚访问过的结点,而指针p指向当前访问的结点,由此记录下遍历过程中访问结点的先后关系。

以结点p为根的子树中序线索化:

        首先我们来判断指针p,如果p非空,左子树递归线索化。如果p的左孩子为空,则给p加上左线索,将其LTag置为1,让p的左孩子指针指向prc (前驱);否则将p的LTag置为0。然后我们来看pre,如果pre的右孩子为空,则给pre加上右线索,将其RTag置为1,让pre的右孩子指针指向p(后继);否则将pre的RTag置为0。之后将pre指向刚访问过的结点p,即Pre=p。最后右子树递归线索化。

代码描述:

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{pre->RTag=0;}pre=p;    //保持pre指向p的前驱InThreading(pre->rchild);    //右子树递归线索化}
}

3.2、遍历线索二叉树 


        由于有了结点的前驱和后继信息,线索二叉树的遍历和在指定次序下查找结点的前驱和后继算法都变得简单。因此,若需经常查找结点在所遍历线性序列中的前驱和后继,则采用线索链表作为存储结构。

        下面讨论在中序二叉树中如何查找结点的前驱与后继:
①查找p指针所指结点的前驱:

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

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

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

        由于有了结点的前驱和后继的信息,线索二叉树的遍历操作无需设栈,避免了频繁的进栈、出栈,因此在时间和空间上都较遍历二叉树节省。如果遍历某种次序的线索二叉树,则只要从该次序下的根结点出发,反复查找其在该次序下的后继,直到叶子结点。所以我们就能按照下面的步骤来进行线索二叉树的遍历啦(以中序线索二叉树的遍历为例):

遍历步骤:

  1. 指针p指向根结点。
  2. p为非空树或遍历未结束时,执行以下操作:
    a、沿左孩子向下,到达最左下结点*p,它是中序的第一个结点;
    b、访问*p;
    c、沿右线索反复查找当前结点*p的后继结点并访问,直至右线索为0或者遍历结束;
    d、转向p的右子树。

代码描述:

void InOrderTraverse_Thr(BiThrTree T){//T指向头结点,头结点的左链lchild指向根结点。//中序遍历的非递归算法,对每个元素直接输出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;cout<<p->data;    //沿右线索访问后继结点}p=p->rchild;    //转向p的右子树}
}


http://www.ppmy.cn/devtools/169467.html

相关文章

Redis解决缓存击穿问题——两种方法

目录 引言 解决办法 互斥锁&#xff08;强一致&#xff0c;性能差&#xff09; 逻辑过期&#xff08;高可用&#xff0c;性能优&#xff09; 设计逻辑过期时间 引言 缓存击穿&#xff1a;给某一个key设置了过期时间&#xff0c;当key过期的时候&#xff0c;恰好这个时间点对…

Linux:用 runc 构建 ARM 平台容器

文章目录 1. 前言2. 构建 runc2.1 准备 C 交叉编译器2.2 编译 libseccomp 库2.3 编译 runc2.3.1 安装 go 编译器2.3.2 编译 runc 3. 构建 runc 镜像包 测试运行3.1 OCI 规范3.2 手工构建 OCI 镜像3.3 运行 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;…

Flink 通过 Chunjun Oracle LogMiner 实时读取 Oracle 变更日志并写入 Doris 的方案

文章目录 一、 技术背景二、 关键技术1、 Oracle LogMiner2、 Chunjun 的 LogMiner 关键流程3、修复 Chunjun Oracle LogMiner 问题 一、 技术背景 在大数据实时同步场景中&#xff0c;需要将 Oracle 数据库的变更数据&#xff08;CDC&#xff09; 采集并写入 Apache Doris&am…

网络编程中客户端与服务器的搭建与协议包应用

1.客户端的搭建 2.服务器搭建 3.TCP中的粘包现象 tcp协议为了提高发送的效率&#xff0c;会将短时间连续发送的小数据&#xff0c;当做一组数据统一发送 原理是&#xff1a; tcp协议本身存在一个1500字节的缓存区&#xff0c;tcp协议每次write发送数据的时候&#xff0c;总是…

快速入手-基于Django的主子表间操作mysql(五)

1、如果该表中存在外键&#xff0c;结合实际业务情况&#xff0c;那可以这么写&#xff1a; 2、针对特殊的字典类型&#xff0c;可以这么定义 3、获取元组中的字典值和子表中的value值方法 4、对应的前端页面写法

llama源码学习·model.py[3]ROPE旋转位置编码(4)ROPE的应用

一、源码注释 def apply_rotary_emb(xq: torch.Tensor, # 查询矩阵xk: torch.Tensor, # 键矩阵freqs_cis: torch.Tensor, # 旋转嵌入 ) -> Tuple[torch.Tensor, torch.Tensor]:# 首先将xq和xk张量转换为浮点数# 然后使用reshape将最后一个维度拆分为两个维度&#xff0c;每…

《南京日报》专题报道 | 耘瞳科技“工业之眼”加码“中国智造”

在江宁开发区&#xff0c;机器人已不再是科幻电影里的遥远想象&#xff0c;他们就像人类的“同事”&#xff0c;在工地上忙着贴砖、刷墙、搬运、检测&#xff1b; 在体育训练场上帮助运动员矫正姿势&#xff1b; 在医院里帮助医生发现帕金森早期征兆&#xff0c;在智慧工厂里…

C# 派生 详解

1.1派生 继承设计的目的&#xff1a;经常需要扩展现有类型来添加功能&#xff08;行为和数据&#xff09;。 定义派生类 要在类标识符后添加冒号&#xff0c;接着添加基类名称。 注意&#xff1a;1.通过继承&#xff0c;基类的每个成员都出现在派生类构成的链条中。 2.除非明…