数据结构 - 线索树

一、 为什么要用到线索二叉树?

我们先来看看普通的二叉树有什么缺点。下面是一个普通二叉树(链式存储方式):

在这里插入图片描述
乍一看,会不会有一种违和感?整个结构一共有 7 个结点,总共 14 个指针域,其中却有 8 个指针域都是空的。对于一颗有 n 个结点的二叉树而言,总共会有 n+1 个空指针域,这个规律使用所有的二叉树。

这么多的空指针域是不是显得很浪费?我们学习数据结构和算法的重点就是在想法设法地提高时间效率和空间利用率。这么多的指针域就这么白白浪费了,太败家了!

所以我们要想法子好好利用它们,利用它们来帮助我们更好地使用二叉树这个数据结构。

那么如何利用呢?

遍历二叉树的实质是将二叉树中非线性结构的结点转化为线性的序列,然后才能方便我们遍历。

比如上图的中序遍历序列为:DBGEACF。

对于一个线性序列(线性表)来说,它有直接前驱和直接后继的概念。比如在中序遍历序列中,B 的直接前驱为 D,直接后继为 G。

我们之所以能知道 B 的直接前驱和直接后继,是因为我们按照中序遍历的算法,把二叉树的中序遍历序列写出来了,然后根据这个顺序序列说谁的前驱是谁、后继是谁。

直接前驱和直接后继是不能完全直接通过二叉树得到的,因为二叉树中只有双亲和孩子结点之间的直接关系,即二叉树的结点指针域中只存储了其孩子结点的地址。

现在的需求是,我想能直接从二叉树上得到某结点在中序遍历方式下的直接前驱和直接后继。

这时候就需要用到线索二叉树了。

二、 什么是线索二叉树?

当然,我们肯定需要借助结点的指针域来保存直接前驱和直接后继的地址。

其实,在上图的普通二叉树中(以中序遍历得到的序列),部分结点(指针域不为空的结点)是可以找到其直接前驱或后继的,比如结点 E 的左孩子 G 就是结点 E 的直接前驱;结点 A 的右孩子 C 就是结点 A 的直接后继。

但部分结点(指针域为空)是行不通的,比如结点 G 的直接后继是 E,直接前驱是 B,但在二叉树中却不能得出这样的结论。怎么办呢?我们注意到,结点 G 的两个指针域都为 NULL,并未被利用,那么我们使用这两个指针,分别指向其前驱和后继不就好了吗?

在这里插入图片描述
实在是两全其美,天作之合!但是问题并没有解决!

因为我们是利用空指针域来指向前驱或后继的,对于那些指针域不为空的结点,这样是矛盾的,比如结点 E 和结点 B。

既然有矛盾,那么我们就发现产生矛盾的根源,解决矛盾。

产生矛盾的根源是:结点的指针域为空和不为空时,指针的指向矛盾。即,指针不为空时指向孩子和指针为空时指向前驱或后继之间的矛盾。

那么我们对症下药,把指针域为空和不为空给区分出来,清晰地告诉指针:不为空时指向孩子,为空时指向前驱或后继。这就需要我们给两个指针各添加一个标志位。

在这里插入图片描述

并约定以下规则:
left_flag == 0 时,指针 left_child 指向左孩子
left_flag == 1 时,指针 left_child 指向直接前驱
right_flag == 0 时,指针 right_child 指向右孩子
right_flag == 1 时,指针 right_child 指向直接前驱

二叉树的结点要有所变化:

/*线索二叉树的结点的结构体*/
typedef struct Node {
    char data; //数据域
    struct Node *left_child; //左指针域
    int left_flag; //左指针标志位
    struct Node *right_child; //右指针域
    int right_flag; //右指针标志位
} TTreeNode;

有了标志位,一切就能理清了。我们称指向直接前驱和后继的指针为线索。标志位为 0 的指针是指向孩子的指针,标志位为 1 的指针是线索。

一个二叉链表树,结点结构如上,我们将所有空指针都变为线索,这样的二叉树就是二叉线索树。

三、如何创造线索二叉树?

在普通二叉树中,我们想要获取某个结点在某种遍历次序下的直接前驱或后继,每次都需要遍历获取到遍历次序之后才能知道。而在线索二叉树中,我们只需要遍历一次(创造线索二叉树时的遍历),之后,线索二叉树就能“记住”每个结点的直接前驱和后继了,以后都不需要再通过遍历次序获取前驱或后继了。

我们按照某种遍历方式,把普通二叉树变为线索二叉树的过程被称为二叉树的线索化。

接下来,我们用中序遍历的方式,将下面的二叉树线索化为线索二叉树
在这里插入图片描述
将标志位为 1 的指针,按照中序遍历序列,使其指向前驱或后继:
在这里插入图片描述
其中,结点 D 没有直接前驱,结点 F 没有直接后继,故指针为 NULL。

到此,我们算是解决了拥有 n 个结点的二叉树存在 n+1 个空指针域所造成的浪费,解决方式是给每个结点的指针增加一个标志位,以此来利用空指针域。标志位中存储的是 0 或 1 的布尔值,与浪费的空指针域相比,是相对比较划算的。而且使二叉树具有了一种新特性——二叉树中能保存在某种遍历次序下的结点之间的前驱和后继关系。

四、线索化的实现

请注意一点,线索二叉树是由普通二叉树得来的,而且是按某种遍历顺序得来的。因为线索是在知道某个结点的前驱和后继的情况下才能设置,而前驱和后继关系不能通过二叉树直接体现,只能通过遍历二叉树得到的线性序列得出关系。所以要通过某种遍历方式得到具有前驱和后继关系的序列后,才能修改结点的空指针,进而设置线索。

即:线索化的实质是在按照某种遍历次序进行遍历二叉树的过程中修改结点的空指针,使其指向其在该遍历次序下的直接前驱或直接后继的过程。

所以,代码的大体结构还是一样的,我们只需把遍历代码中的打印代码换成线索化的代码,并作出一些其他改变即可。

下面以下图为例,分别介绍三种线索化:

一颗未线索化的二叉树,其所有标志位均默认为 0.

在这里插入图片描述
4.1. 中序线索化

按照中序遍历次序线索化后,可得下图:
在这里插入图片描述
我们先再次明确以下内容:

  • 我们是在遍历二叉树的过程中进行线索化的。
  • 中序遍历的顺序为:左子树 >> 根 >> 右子树。
  • 线索化修改两个东西:空指针域和其对应的标志位。
  • 如何修改?将空指针域置为直接前驱或后继。

所以我们的问题变成了:

  1. 找到所有空指针域。
  2. 找到空指针域所属结点,在先序次序下的直接前驱和直接后继。
  3. 修改空指针域的内容,及其标志位,使该指针称为线索。

说明:我们在遍历二叉树时,使用到了递归,所以在进行线索化的时候,也会使用它。

具体代码如下:

//全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 中序线索化
 */
void inorder_threading(TTreeNode *root)
{
    if (root == NULL) { //若二叉树为空,做空操作
        return;
    }
    inorder_threading(root->left_child);
    if (root->left_child == NULL) {
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
    inorder_threading(root->right_child);
}

4.2. 先序线索化

按照先序顺序线索化后,可得下图:
在这里插入图片描述
具体代码如下:

// 全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 先序线索化
 */
void preorder_threading(TTreeNode *root)
{
    if (root == NULL) {
        return;
    }
    if (root->left_child == NULL) {
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
    if (root->left_flag == 0) {
        preorder_threading(root->left_child);
    }
    if (root->right_flag == 0) {
        preorder_threading(root->right_child);
    }
}

4.3. 后序线索化

按照后序遍历次序线索化后,可得下图:

在这里插入图片描述
具体代码如下:

//全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 后序线索化
 */
void postorder_threading(TTreeNode *root)
{
    if (root == NULL) {
        return;
    }
    postorder_threading(root->left_child);
    postorder_threading(root->right_child);
    if (root->left_child == NULL) {
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
}

五、总结

线索二叉树充分利用了二叉树中的空指针域,给予二叉树一个新特性——通过一次遍历进行线索化后,二叉树中就能保存其结点之间的前驱和后继关系。

所以,如果我们需要频繁遍历二叉树,查找某个结点的直接前驱或后继结点,使用线索二叉树是非常合适的。

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

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

相关文章

Public Key Retrieval is not allowed 异常解决方法 240204

Public Key Retrieval is not allowed 异常解决方法 : 将 allowPublicKeyRetrieval 设置为: allowPublicKeyRetrievaltrue allowPublicKeyRetrievalallowPublicKeyRetrievaltruejdbc:mysql://localhost:3306/DatabaseName?allowPublicKeyRetrievaltrue&autoReconnecttrue…

谷歌seo搜索引擎优化有什么思路?

正常做seo哪有那么多思路,其实就那么几种方法,无非就关键词,站内优化,外链,可以说万变不离其宗,但如果交给我们,你就可以实现其他的思路,或者说玩法 收录可以说是一个网站的基础&…

深度学习入门笔记(九)自编码器

自编码器是一个无监督的应用,它使用反向传播来更新参数,它最终的目标是让输出等于输入。数学上的表达为,f(x) x,f 为自编码器,x 为输入数据。 自编码器会先将输入数据压缩到一个较低维度的特征,然后利用这…

Web项目利用EasyExcel实现Excel的导出操作

早期Java使用的一些解析,到处excel的框架存在种种问题被遗弃,现在使用阿里巴巴所提供的EasyExcel已成为一种主流,本篇将详细介绍该功能在Web项目中如何实际应用。 详细操作文档:写Excel | Easy Excel 一、项目演示 在后台管理界…

Java学习网络编程

Java学习网络编程 大纲 网络相关概念IP地址网络协议InetAdressSocket 具体案例 1. 网络相关概念 网络 网络通信 2. IP地址 域名 3.网络协议 4. InetAdress 获得本机的名字和IP public static void main(String[] args) throws UnknownHostException {InetAddress inetA…

springboot172基于springboot的二手车交易系统的设计与实现

二手车交易系统的设计与实现 摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统二手车交易信息管理难度大&…

day06.C++排序(整理)

一.直接插入排序 void Insertsort(int *a,int n){int i,j;for( i1;i<n;i){if(a[i]<a[i-1]){int tempa[i];//哨兵for( ji-1;temp<a[j];j--){a[j1]a[j];//记录后移}a[j1]temp;//插入到正确位置}} }二.希尔排序 void Shellsort(int *a,int n){for(int dltan/2;dlta>…

Redis核心技术与实战【学习笔记】 - 31.番外篇:Redis客户端如何与服务器端交换命令和数据

简述 Redis 使用 RESP 协议&#xff08;Redis Serialzation Protocol&#xff09;协议定义了客户端和服务器端交互的命令、数据的编码格式。在 Redis 2.0 版本中&#xff0c;RESP 协议正式称为客户端和服务器端的标准通信协议。从 Redis 2.0 到 Redis 5.0 &#xff0c;RESP 协…

Office2007下载安装教程,保姆级教程,附安装包和工具

前言 Microsoft Office是由Microsoft(微软)公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、PowerPoint、Access、Outlook等。 准备工作 1、Win7 及以上系统 2、提前准备好 Office 2007 安装包 安装步骤 1.鼠标右击【Office2007】压缩包&…

containerd中文翻译系列(十五)转运服务

传输服务是一种简单灵活的服务&#xff0c;可用于在源和目的地之间传输人工制品对象。灵活的应用程序接口&#xff08;API&#xff09;允许传输接口的每个实施方案决定是否可以在源和目的地之间进行传输。这样&#xff0c;实现者就可以直接添加新功能&#xff0c;而无需对应用程…

Flask基础学习

1.debug、host、port 模式修改 1) debug模式 默认debug模式是off&#xff0c;在修改代码调试过程中需要暂停重启使用&#xff0c;这时可修改on模式解决。 同时在debug模式开启下可看到出错信息。 下面有关于Pycharm社区版和专业版修改debug模式的区别 专业版 社区版&#…

Python常见的免杀方式

10.1节介绍了通过msfvenom生成shellcode &#xff0c;并通过Python程序加载执行&#xff0c;又 介绍了如何将Python的.py文件生成为exe文件。使用pyinstaller生成的可执行文件 本身就具有一定的免杀能力&#xff0c;但是在与杀毒软件对抗时&#xff0c;部分杀毒软件也可以通 过…

静态时序分析:静态时序分析的原理及其两种模式PBA、GBA

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 静态时序分析有两种模式&#xff1a;PBA(Path Based Analysis)和GBA(Graph Based Analysis)&#xff0c;PBA是基于路径的分析模式而GBA则是基于图的分析模式。在…

2019 年全国职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书(笔记详解)

1. 网络拓扑图 2. IP 地址规划表 3. 设备初始化信息 阶段一 任务 1:网络平台搭建 1、根据网络拓扑图所示,按照 IP 地址参数表,对 DCFW 的名称、各接口IP 地址进行配置。 2、根据网络拓扑图所示,按照 IP 地址参数表,对 DCRS 的名称进行配置,创建 VLAN 并将相应接口划入 …

SpringBoot WebSocket客户端与服务端一对一收发信息

依赖 <!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>配置类 Configuration public class WebSocketConfig {Bean //方法返回值交…

设计模式巡礼:多板适配案例解析与深度重构

theme: cyanosis 月黑风高&#xff0c;好兄弟发给我一个重构需求&#xff0c;咨询我的意见。 一、 场景分析 开发的产品是需要运行到不同的定制Android板子&#xff0c;不同板子有对应的不同SDK提供的API&#xff0c;目前的业务端&#xff0c;业务流程基本是确定的&#xff0…

表单标记(html)

前言 发现input的type属性还是有挺多的&#xff0c;这里把一些常用的总结一下。 HTML 输入类型 (w3school.com.cn)https://www.w3school.com.cn/html/html_form_input_types.asp text-文本 文本输入,如果文字太长&#xff0c;超出的部分就不会显示。 定义供文本输入的单行…

玩转rk3588(六):rk3588使用ffmpeg实现硬件解码,解决opencv中VideoCapture获取网络摄像头视频时,一直在open时返回false的问题(一)

目录 0、前言 1、开发环境 2、安装rkmpp 3、安装x264 4、安装libdrm 5、安装ffmpeg 6、相关报错 1&#xff09;libdrm编译过程中报错 2&#xff09;ffmpeg: error while loading shared libraries: libavdevice.so.60: cannot open shared object file: No such file …

OpenShift AI - 运行欺诈检测模型和流程

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.14 RHODS 2.50 的环境中验证 文章目录 准备运行环境安装 OpenShift AI 环境安装 Minio 对象存储软件创建 Data Science Project创建 Data connection创建 Workbench配置 Model server创建 …

Unity类银河恶魔城学习记录5-1.5-2 P62-63 Creating Player Manager and Skill Manager源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili PlayerManager.cs using System.Collections; using System.Collections.G…