数据结构(超详细讲解!!)第二十五节 线索二叉树

news2024/11/27 15:35:27

1.线索二叉树的定义和结构  

问题的提出:

通过遍历二叉树可得到结点的一个线性序列,在线性序列中,很容易求得某个结点的直接前驱和后继。但是在二叉树上只能找到结点的左孩子、右孩子,结点的前驱和后继只有在遍历过程中才能得到,那么,如何保存遍历二叉树后动态得到的线性序列,以便快速找到某个结点的直接前驱和后继?  

分析:    

n个结点有n-1个前驱和n-1个后继;    

一共有2n个链域,其中:n+1个空链域,n-1个指针域;    

因此, 可以用空链域来存放结点的前驱和后继。    线索二叉树就是利用n+1个空链域来存放结点的前驱和后继结点的信息。

定义:

规定,若结点有左孩子,则其lchild指示其左孩子,否则,令lchild域指示其前驱;若结点有右孩子,则其rchild指示其右孩子,否则,令rchild域指示其后继。为了表示lchild和rchild域指向的是左、右孩子还是前驱、后继,可以加两个标志域,以明确lchild和rchild的指向。  

结点结构:

 若结点有左子树,则左链域lchild指示其左孩子(ltag=0);否则,令左链域指示其前驱(ltag=1);    

若结点有右子树,则右链域rchild指示其右孩子(rtag=0);否则,令右链域指示其后继(rtag=1);

//线索二叉树的类型定义:
typedef  struct BiThrNode
{	ElemType data ;
	struct BiThrNode * lchild , * rchild ;
	int ltag , rtag ;
}BiThrNode , * BiThrTree ;

其中:

ltag  = 0     lchild域指示结点的左孩子      

ltag =  lchild域指示结点的前驱     

rtag = 0    rchild域指示结点的右孩子

  以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向前驱和后继的指针,叫做线索(Thread)。加上线索的二叉树叫做线索二叉树(Thread Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。

 为了操作方便,在存储线索二叉树时增设一个头结点,其结构与其他的线索二叉树的结点相同,只是数据域不存储任何数据,其左指针域指向二叉树的根结点,右指针域指向遍历的最后一个结点,而二叉树在某种遍历下的第一个结点的前驱和最后一个结点的后继线索都指向头结点。 

2.二叉树的线索化

基本思想:        

二叉树的线索化实质上是遍历一棵二叉树。在遍历过程中,访问结点的操作是检查此结点的左、右指针域是否为空,如果为空,将它改为指向其前驱或后继结点的线索。

本节以中序线索化为例说明其线索化过程。        

为实现这一过程,设指针p指向当前结点,pre始终指向刚刚访问过的结点,即p的前驱,以便于修改pre的后继线索和p的前驱线索。在线索化算法中访问当前结点p的处理方法如下:        

①若结点p的左指针域为空,则将其标志位置为1,并使 p->lchild指向中序前驱结点pre(即左线索化);        

②若结点pre的右指针域为空,则将其标志位置为1,并使pre->rchild指向中序后继结点p(即右线索化);        

③将pre指向刚刚访问过的结点p(即pre=p),线索化p的右子树。

实现算法如下:

BiThrTree pre ;		/* 全局变量pre始终指向刚访问的结点*/
int InOrderThread ( BiThrTree * head , BiThrTree bt )
{	*head = (BiThrTree)malloc(siazeof( BiThrNode)) ;
	if ( * head == NULL ) return 0;/* 线索化时头结点空间分配失败,返回 */
	( * head ) -> ltag = 0 ; ( * head ) -> rtag = 1 ;
	( * head ) -> rchild = * head ;	/* 右指针回指 */
	if ( bt == NULL ) ( * head ) -> lchild = * head ;/* 空二叉树左指针回指 */
	else 
	{	( * head ) -> lchild = bt;  /*头结的左孩子指针指向二叉树的根结点*/
		pre = * head ;
		InThreading ( bt ) ;		/* 中序遍历二叉树并线索化 */
		pre -> rchild = * head ;
		pre -> rtag = 1 ;		/* 最后一个结点线索化 */
		( * head ) -> rchild = pre ;
	}
	return 1 ;
}
void InThreading ( BiThrTree p )
{	if ( p )
	{     InThreading ( p-> lchild ) ;
	      if(p->lchild==NULL)   /* p无左孩子,左指针域为线索 */
	      {   p->ltag=1 ; 		p–>lchild=pre ;  }
	      if (pre->rchild==NULL)    /*pre无右孩子,其右指针域为线索*/
	       {   pre->rtag=1;	pre->rchild=p;  }	
	      pre = p ;
	      InThreading ( p -> rchild ) ;
	}
}

线索二叉树中结点的前驱和后继查找

1、中序线索二叉树中结点p的中序前驱结点      

对于中序线索二叉树上的任一节点p,查找其中序的前驱结点有下面两种情况:      

(1)若该结点的ltag = 1,那么其左指针域指向的结点就是结点p的前驱结点。    

(2)若该结点的ltag = 0,则该结点有左孩子,根据中序遍历的定义,其前驱结点是以该结点的左孩子为根结点的子树的最右、最下结点(中序遍历该子树时最后一个访问结点),即沿着其左子树的右指针域向下查找,当某结点的右标志域为1时,它就是所要找的前驱结点。

//中序线索二叉树查找结点的前驱节点算法如下:
BiThrTree InPreNode(BiThrTree p)
{	BiThrTree pre;
	pre = p->lchild;
	if(p->ltag != 1)		/* 结点p有左孩子*/
	   while(pre->rtag == 0) 	pre = pre->rchild;
		/* 从左子树的根结点开始,沿右指针域往下查找,
                                直到没有右孩子为止 */
	return pre;			/* 返回结点p的前驱结点*/
}

2、中序线索二叉树中结点p的中序后继结点      

对于中序线索二叉树中的任一节点p,查找其中序的后继结点有下面两种情况。      

(1)如果该结点rtag = 1,则其右指针域指向的结点就是结点p的后继结点。      

(2)如果该结点rtag = 0,则该结点有右孩子,根据中序遍历的定义,它的后继结点是以该结点的右孩子为根结点的子树的最左、最下结点(中序遍历该子树时第一个访问的结点),即沿着其右子树的左指针域向下查找,当某结点的左标志域为1时,它就是所要找的后继结点。

//中序线索二叉树查找结点的后继节点算法如下:
BiThrTree InPostNode(BiThrTree p)
{	BiThrTree post;
	post = p->rchild;
	if(p->rtag != 1)	/* 结点p有右孩子*/
		while(post->ltag == 0)	post = post->lchild;
		/* 从右子树的根结点开始,沿左指针域往下查找,直到没有左孩子为止 */
	return post;		/* 返回结点p的后继结点*/
}

3.思考

1.如何在先序线索树中查找结点p的后继?

 在先序线索树中找结点的后继比较容易,根据先序线索树的遍历过程可知:      

若结点p存在左子树,则p的左孩子结点即为p的后继;    

 若结点p没有左子树,但有右子树,则p的右孩子结点即为p的后继;      

若结点p既没有左子树,也没有右子树,则结点p的RChild指针域所指的结点即为p的后继。

用语句表示则为:

if (p->Ltag==0) succ=p->LChild else succ=p->RChild

2.在先序线索树中如何找结点的前驱?

若结点p是二叉树的根,则p的前驱为空; 若p是其双亲的左孩子,或者p是其双亲的右孩子并且其双亲无左孩子,

则p的前驱是p的双亲结点;

若p是双亲的右孩子且双亲有左孩子,

则p的前驱是其双亲的左子树中按先序遍历时最后访问的那个结点。

4.删除插入操作

1) 插入结点运算        

在中序线索二叉树上插入结点可以分两种情况考虑:第一种情况是将新的结点插入到二叉树中,作某结点的左孩子;第二种情况是将新的结点插入到二叉树中,作某结点的右孩子。 下面我们仅讨论后一种情况。          

InsNode(BiThrNode *p, BiThrNode *r)表示在线索二叉树中插入r所指向的结点,做p所指结点的右孩子。此时有两种情况:

(1) 若结点p的右孩子为空,则插入结点r的过程很简单。        

原来p的后继变为r的后继        

结点p变为r的前驱        

结点r成为p的右孩子      

结点r的插入对p原来的后继结点没有任何的影响。

(2) 若p的右孩子不为空

p的右孩子变为r的右孩子结点

p变为r的前驱结点

r变为p的右孩子结点

这时还需要修改原来p的右子树中“最左下端”结点的左指针域,使它由原来的指向结点p变为指向结点r 

 若p的右孩子不为空,则插入后,p的右孩子变为r的右孩子结点, p变为r的前驱结点,r变为p的右孩子结点。这时还需要修改原来p的右子树中“最左下端”结点的左指针域,使它由原来的指向结点p变为指向结点r。

void InsNode(BiThrNode *p, BiThrNode *r)
{if(p->Rtag==1)    /* p无右孩子 */
         {
               r->RChild=p->RChild;   /* p的后继变为r的后继 */
               r->Rtag=1; 
               p->RChild=r;    /* r成为p的右孩子 */
               r->LChild=p;   /* p变为r的前驱 */
               r->Ltag=1; 
}
Else     /* p有右孩子 */
  {
        s=p->RChild; 
       while(s->Ltag==0)
        s=s->LChild;     /* 查找p结点的右子树的“最左下端”结点 */
        r->RChild=p->RChild;    /* p的右孩子变为r的右孩子 */
        r->Rtag=0;  
        r->LChild=p;    /* p变为r的前驱 */
        r->Ltag=1; 
        p->RChild=r;     /* r变为p的右孩子 */
        s->LChild=r;     /* r变为p原来右子树的“最左下端”结点的前驱 */
   }
} 

将新结点r插入到中序线索二叉树中作结点p的左孩子的算法与上面的算法类似。

2)删除结点运算

与插入操作一样,在线索二叉树中删除一个结点也会破坏原来的线索,所以需要在删除的过程中保持二叉树的线索化。显然,删除操作与插入操作是一对互逆的过程。

例如,在中序线索二叉树中删除结点r的过程如图所示。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1254656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机视觉面试题-02

图像处理和计算机视觉基础 什么是图像滤波?有哪些常见的图像滤波器? 图像滤波是一种通过在图像上应用滤波器(卷积核)来改变图像外观或提取图像特征的图像处理技术。滤波器通常是一个小的矩阵,通过在图像上进行卷积…

【Linux】第二十站:模拟实现shell

文章目录 一、shell的实现细节1.shell的一些细节2.用户名、主机名、工作目录2.输入命令3.改为循环4.切割字符串5.普通命令的执行6.内建命令的处理7.子进程的退出码8.总结 二、模式实现shell完整代码 一、shell的实现细节 1.shell的一些细节 shell操作系统的一个外壳程序。 s…

【JavaEE初阶】浅谈进程

✏️✏️✏️今天正式进入JavaEE初阶的学习,给大家分享一下关于进程的一些基础知识。了解这部分内容,只是为后续多线程编程打好基础,因此进程部分的知识,不需要了解更加细节的内容。 清风的CSDN博客 😛😛&a…

Unsupervised Skill Discovery via Recurrent Skill Training论文笔记

Zheyuan Jiang, Jingyue Gao, Jianyu Chen (2022). Unsupervised Skill Discovery via Recurrent Skill Training. In Conference on Neural Information Processing Systems (NeurIPS), 2022. 通过循环技能训练发现无监督技能 1、Motivation 以往的无监督技能发现方法主要使…

Spring面向切面编程(AOP);Spring控制反转(IOC);解释一下Spring AOP里面的几个名词;Spring 的 IoC支持哪些功能

文章目录 Spring面向切面编程(AOP)什么是AOPSpring AOP and AspectJ AOP 的区别?Spring AOP中的动态代理如何理解 Spring 中的代理?解释一下Spring AOP里面的几个名词Spring在运行时通知对象Spring切面可以应用5种类型的通知:什么是切面 Aspe…

【开源】基于Vue+SpringBoot的食品生产管理系统

项目编号: S 044 ,文末获取源码。 \color{red}{项目编号:S044,文末获取源码。} 项目编号:S044,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3…

Typescript基础面试题 | 05.精选 ts 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

代码随想录算法训练营第四十九天|123. 买卖股票的最佳时机III 、188. 买卖股票的最佳时机 IV

LeetCode 123. 买卖股票的最佳时机 III 题目链接:123. 买卖股票的最佳时机 III - 力扣(LeetCode) 这个道题和121. 买卖股票的最佳时机 I、122. 买卖股票的最佳时机 II很像,是两题的结合。 我们就定义两个数组来实现。 代码&…

【Apache Doris】Manager极致丝滑地运维管理

【Apache Doris】Manager极致丝滑地运维管理 1.标准VS可视化运维管理2. 环境信息2.1.硬件信息2.2.软件信息 3.前置准备3.1.安装包准备3.2.文档手册准备 4.集群初始化4.1.系统参数预设4.2.Manager部署4.3.新集群部署4.4 监控告警4.4.1 监控4.4.2 告警 5. 集群升级5.1 新包准备5.…

C#,《小白学程序》第二十一课:大数的减法(BigInteger Subtract)

1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary> /// 大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算 /// 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法 /// </summary> p…

第二十一章 解读XML与JSON文件格式(工具)

XML 带分隔符的文件仅有两维的数据&#xff1a;行 & 列。如果我们想在程序之间交换数据结构&#xff0c;需要一种方法把层次结构&#xff0c;序列&#xff0c;集合和其它的数据结构编码成文本。 今天要说的 XML 是最突出的处理上述这种转换的标记格式&#xff0c;它使用标…

01、copilot+pycharm

之——free for student 目录 之——free for student 杂谈 正文 1.for student 2.pycharm 3.使用 杂谈 copilot是github推出的AI程序员&#xff0c;将chatgpt搬到了私人终端且无token限制&#xff0c;下面是使用方法。 GitHub Copilot 是由 GitHub 与 OpenAI 合作开发的…

智能AI名片-Pushmall推贴SCRM数字名片的发展趋势

智能AI名片-Pushmall推贴SCRM数字名片的发展趋势 基于相识靠铺人脉相互引荐&#xff0c;共享人脉资源&#xff0c;众筹共创赋能交友、商务实现大众创业&#xff0c;灵活创收的智能AI名片平台。帮助企业实现成员管理与客户资源管理。功能说明 1、搜索查询&#xff1a;个人信息与…

Leetcode—828.统计子串中的唯一字符【困难】

2023每日刷题&#xff08;四十一&#xff09; Leetcode—828.统计子串中的唯一字符 算法思想 枚举所有种类字母在s中出现的位置&#xff0c;分别统计只包含这个字母不包含该类字母中其他字母的子串个数 实现代码 int uniqueLetterString(char* s) {int len strlen(s);cha…

Quartz定时任务基础

springBoot有一个定时执行某个方法的 注解&#xff1a; Scheduled 可以满足挺多的需求&#xff0c;但是到了一些场景&#xff0c;就显得比较麻烦&#xff0c;比如&#xff1a; 机器待机五分钟后执行切换待机状态。如果是按照使用Scheduled注解&#xff0c;就得持久化一个表&…

【5G PHY】5G SS/PBCH块介绍(四)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

利用ngrok实现内网穿透(全网最详细教程)

准备工具&#xff1a; 1、phpstudy 用于在本地搭建网站 2、ngrok 用于将自己的本地端口暴露到公网上&#xff0c;从而实现内网穿透 文章开始前给大家分享一个学习人工智能的网站&#xff0c;通俗易懂&#xff0c;风趣幽默 人工智能https://www.captainbed.cn/myon/ ~~~~~…

C#文件基本操作(判断文件是否存在、创建文件、复制或移动文件、删除文件以及获取文件基本信息)

目录 一、判断文件是否存在 1.File类的Exists()方法 2.FileInfo类的Exists属性 二、创建文件 1.File类的Create()方法 2.FileInfo类的Create()方法 三、复制或移动文件 1.File类的Copy()方法 2.File类的Move()方法 3.FileInfo类的CopyTo()方法 四、删除文件 1.File…

大数据数据仓库,Sqoop--学习笔记

数据仓库介绍 1. 数据仓库概念 数据仓库概念创始人在《建立数据仓库》一书中对数据仓库的定义是&#xff1a;数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的&#xff08;Subject Oriented&#xff09;、数据集成的&#xff08;Integrated&#xff09;、相对…

【AUTOSAR】【通信栈】ComXf

AUTOSAR专栏——总目录_嵌入式知行合一的博客-CSDN博客文章浏览阅读292次。本文主要汇总该专栏文章,以方便各位读者阅读。https://xianfan.blog.csdn.net/article/details/132072415 目录 一、概述 二、限制说明