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

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.mfbz.cn/a/193427.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python中的简单线性拟合

简单线性回归可以拟合线性关系的数据,一般使用一次函数或二次函数即可。 import numpy as np import matplotlib.pyplot as pltxnp.array([1,2,3,4,5,6,7,8,9,10]) ynp.array([2.5,4.5,4.8,5.5,6.0,7.0,7.8,8.0,9.0,10.0])#一次拟合函数 slope,interceptnp.polyfit…

TUP通信——与多个客户端同时通信

一,概括:可以通过多线程思想每加一个客户端由线程池中的主线程交给一个子线程管理 二,案例 (1),线程池 (2),服务端 (3),客户端

从零开始学优惠券样式代码编写,让你的网站焕然一新!

样式1&#xff1a; 代码实例&#xff1a; <div class"box"><div class"itemBox"><div class"leftBox">全额抵扣</div><div class"rightBotton"><button>立即使用</button></div><…

FLV 文件格式分析

前言 flv 是 flash video 的缩写&#xff0c;是 Adobe Flash payler 支持的一种流媒体播放格式。flv 是一种层级格式&#xff0c;除了一个 flv header 外&#xff0c;剩下全是由 一个个 tag 组成。tag 是由 tag 头和 tag 数据组成。tag 类型分为音频、视频、脚本&#xff0c;一…

Kafka 如何保证消息消费的全局顺序性

哈喽大家好&#xff0c;我是咸鱼 今天我们继续来讲一讲 Kafka 当有消息被生产出来的时候&#xff0c;如果没有指定分区或者指定 key &#xff0c;那么消费会按照【轮询】的方式均匀地分配到所有可用分区中&#xff0c;但不一定按照分区顺序来分配 我们知道&#xff0c;在 Kaf…

redis笔记 -- 基础数据结构

redis笔记 基础的数据结构&#xff1a;string、list、hash、set、zset 容器型数据结构&#xff08;list、hash、set、zset&#xff09;通用规则 如果容器不存在&#xff0c;就创建一个&#xff0c;再进行操作如果容器里没有数据了&#xff0c;就立即删除&#xff0c;回收内存…

数字IC芯片验证流程及验证工具推荐?收藏专用

验证其实是一个“证伪”的过程&#xff0c;从流程到工具&#xff0c;验证工程师的终极目的都只有一个&#xff1a; 发现所有BUG&#xff0c;或者证明没有BUG&#xff0c;以保证芯片功能性能的正确性和可靠性。 验证环节对于一颗芯片的重要性也是不言而喻的&#xff1a; 从项…

python爬虫指南之请求模块urllib的详细教程

文章目录 前言一、urllib的子模块二、HttpResponse常用方法与属性获取信息urlli.parse的使用(一般用于处理带中文的url) 三、爬取baidu官网HTML源代码添加请求头信息&#xff08;重构user\_agent&#xff09; 四、扩展知识with open和open两者的区别关于Python技术储备一、Pyth…

python+gurobi求解线性规划、整数规划、0-1规划

文章目录 简单回顾线性规划LP整数规划IP0-1规划 简单回顾 线性规划是数学规划中的一类最简单规划问题&#xff0c;常见的线性规划是一个有约束的&#xff0c;变量范围为有理数的线性规划。如&#xff1a; 使用matlab的linprog函数即可求解简单的线性规划问题&#xff0c;可以参…

人力资源管理后台 === 角色管理

目录 1.组织架构-编辑部门-弹出层获取数据 2.组织架构-编辑部门-编辑表单校验 3.组织架构-编辑部门-确认取消 4.组织架构-删除部门 5.角色管理-搭建页面结构 6.角色管理-获取数据 7.角色管理-表格自定义结构 8.角色管理-分页功能 9.角色管理-新增功能弹层 10.角色管理…

springboot实现验证码功能

转载自 : www.javaman.cn 1、编写工具类生成4位随机数 该工具类主要生成从0-9&#xff0c;a-z&#xff0c;A-Z范围内产生的4位随机数 /*** 产生4位随机字符串*/public static String getCheckCode() {String base "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn…

【TinyALSA全解析(三)】tinyplay、tincap、pcm_open源码解析

tinyplay、tincap、pcm_open源码解析 一、本文的目的二、tinyplay.c源码分析三、tinycap.c源码分析四、pcm.c如何调度到Linux Kernel4.1 pcm_open解析4.1.1 pcm_open的主要流程4.1.2 流程说明4.1.3 调用方法 4.2 pcm_write解析 /*********************************************…

文章改写工具-改写神器

当代社会&#xff0c;信息爆炸&#xff0c;写作已成为人们生活与工作中不可或缺的一环。无论是学术论文、商业报告还是日常沟通&#xff0c;文字的准确表达和精彩呈现是至关重要的。然而&#xff0c;许多人在面对写作时&#xff0c;常常为语言表达、词汇选择而苦恼。为了解决这…

基于OpenCV+YOLOv5实现车辆跟踪与计数(附源码)

导 读 本文主要介绍基于OpenCVYOLOv5实现车辆跟踪与计数的应用&#xff0c;并给出源码。 资源下载 基础代码和视频下载地址&#xff1a; https://github.com/freedomwebtech/win11vehiclecount main.py代码:​​​​​​​ import cv2import torchimport numpy as npfrom tr…

Dockerfile讲解

Dockerfile 1. 构建过程解析2. Dockerfile常用保留字指令3. 案例3.1. 自定义镜像mycentosjava83.2. 虚悬镜像 4. Docker微服务实战 dockerfile是用来构建docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 dockerfile定义了进程需要的一切东西&…

hdlbits系列verilog解答(Exams/m2014 q4e)-46

文章目录 一、问题描述二、verilog源码三、仿真结果 一、问题描述 实现以下电路&#xff1a; 二、verilog源码 module top_module (input in1,input in2,output out);assign out ~(in1 | in2);endmodule三、仿真结果 转载请注明出处&#xff01;

JOSEF 综合继电器 HJZZ-32/2 AC220V 合闸延时整定0.02-9.99S

系列型号&#xff1a; HJZZ-91分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/1分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/2分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/2A分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/3分闸、合闸、电源监视综…

Gitee上传代码教程

1. 本地安装git 官网下载太慢&#xff0c;我们也可以使用淘宝镜像下载&#xff1a;CNPM Binaries Mirror 安装成功以后电脑会有Git Bush标识&#xff0c;空白处右键也可查看。 2. 注册gitee账号&#xff08;略&#xff09; 3. 创建远程仓库 4. 上传代码 4.1 在项目文件目录…

【教学类-06-10】20231126 X-Y数字分合-分-下空左

结果展示&#xff1a; 背景需求&#xff1a; 数字分合&#xff0c;这一次空在左侧 代码展示&#xff1a; X-Y 之间的分合题-分-空在右侧 时间&#xff1a;2023年11月26日 21:46 作者&#xff1a;阿夏 import random from win32com.client import constants,gencache from win3…

python之静态服务器程序开发

文章目录 Python静态Web服务器开发Web静态服务器初识搭建Python自带的静态Web服务器静态Web服务器返回固定页面数据静态Web服务器返回指定页面数据静态Web服务器多任务版静态Web服务器面向对象开发静态Web服务器命令行启动动态绑定端口号 Python静态Web服务器开发 Web静态服务…
最新文章