数据结构难点——线索二叉树的详解

线索二叉树

  • 线索二叉树的出现
  • 中序二叉树
    • 不使用中序线索二叉树
    • 使用中序线索二叉树(难点)

注:
在阅读这篇文章之前,建议读者先掌握递归的概念和原理,以便更好地理解和欣赏文章中的内容。

线索二叉树的出现

普通二叉树的查询元素都必须从根结点开始,从头开始遍历,但是如果只对二叉树元素中一个非根结点的元素,要想查找其前驱结点和后继结点是十分不方便的(虽然可以实现,采用双指针的方式即可,但是麻烦且算法效率低)

于是人们研究出了线索二叉树来解决这个问题

定义二叉树时,不管是先序遍历,中序遍历还是后续遍历,都存在大量的空指针,于是利用这些空指针,将其指向元素的前驱结点和后驱结点(两者存在的时候),就可以建立线索二叉树。

本文章将以中序二叉树为例子来,重点解释线索二叉树的作用和与普通二叉树的不同之处

中序二叉树

请添加图片描述

中遍历结果为:D B E A F C G

借助中序遍历后的结果,可以让我们更直观的感受到元素的前驱结点和后继结点

不使用中序线索二叉树

不使用中序线索二叉树的思路为:

  1. 从根结点出发,重新进行一次中序遍历,但是这次用两个指针q,pre来记录
  2. 指针q记录当前访问的结点,指针pre记录上一次被访问的结点(即q比pre多走一步)
  3. 当q指针访问的结点是我们的目标结点时,结束,此时pre就是其前驱结点
  4. 同时,如果要确定其后继结点时,则在让pre和q继续运行一次,让pre = 目标结点,这是q就是目标结点的后继指针

结论:

  1. 使用两个指针p,pre,进行中序遍历
  2. 当q == 目标结点 时,pre为前驱结点
  3. 当 pre == 目标结点 时,q为后继结点

这里用两个指针进行中序遍历,明显增加了程序的运行时间,并且使用起来也比较繁琐。

使用中序线索二叉树(难点)

中序二叉树就是将结点的内容扩大,之前的结点只存储指向其双亲结点的指针,现在扩充其范围,添加指向前驱和后继的两个指针和判定是否存在前驱或后继结点的判断区域
请添加图片描述
其中

  • *lchild *rchild存放其前驱和后继结点
  • ltag rtag判断是否存在前驱和后继结点,用1,0来表示是否存在

图示:
请添加图片描述
请添加图片描述
注:读者在阅读下面思路的时候,可以边画图边理解,这样更方便理解。
思路(结合下面的代码层面仔细阅读,理解是如何遍历二叉树的,是如何建立线索从而构建中序二叉树的):

  1. 用p指针遍历左子树的时候,会一直访问到D结点,因为D结点没有左孩子,于是将D结点的左孩子赋值为pre(此时pre为空),然后将pre变成D,再执行InThread(p->rchild,pre);
  2. 此时p为结点G,但是pre还是结点D,这时候执行代码p->lchild = pre;,G的左孩子(前驱结点)指向D结点,再执行pre->rchild = p,D的右孩子(后继结点)指向G结点,同时,这一躺遍历结束,pre指向G;
  3. 将pre给到G结点之后,从A->B->D->G这一趟的遍历结束;
  4. 由于递归是采用栈的形式进行的,这时候,将G,D出栈,开始对B结点进行操作
  5. 注意,这是给到结点B时,不需要再进行InThread(p->lchild,pre);这一步,因为遍历左边已经结束,出栈给到了B,直接开始执行后面的代码。
  6. 此时执行if(!pre->rchild)语句段,这时候,将G结点的右指针给到了B,此时G结点已完成线索的搭建,然后将pre给到B结点,然后p给到A结点。
  7. 此时pre为B结点,执行if(!pre->rchild)语句段,将B的右孩子给到A结点,此时,左子树已经完成了所有的线索搭建
  8. 右子树和左子树一样,结合递归就可以理解。

代码示例:

//完整过程
//线索二叉树的结构体
typedef struct node
{
    ElemType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;      //记录是否存在前驱,后继结点
} ThreadNode;

//创建
void CreatInThreed(ThreadNode *T)
{
    ThreadNode *pre = NULL;
    if(T != NULL)
    {
        InThread(T,pre);
        pre->rchild = NULL;
        pre->rtag = 1;
    }
}

//线索化二叉树
void InThread(ThreadNode *p,ThreadNode *pre)
{
    if(p)
    {
        InThread(p->lchild,pre);//递归遍历,直到左孩子不存在时递归完毕
        if(!p->lchild)      //p->child为空时执行
        {
            p->lchild = pre;
            p->ltag = 1;
        }
        if(!pre->rchild)    //pre->child为空时执行
        {
            pre->rchild = p;    //将前驱结点的后继指针指给p
            pre->rtag = 1;
        }
        pre = p;
        InThread(p->rchild,pre);
    }
}

最终图示:
请添加图片描述

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

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

相关文章

NFT交易市场-后端开发

首先我们需要配置好我们的ipfs,参考官方文档 1.https://docs.ipfs.tech/install/command-line/#system-requirementshttps://docs.ipfs.tech/how-to/command-line-quick-start/#initialize-the-repository 首先新建一个文件夹 然后在终端输入npm init -y命令进行初…

docker 和K8S知识分享

docker知识: 比如写了个项目,并且在本地调试没有任务问题,这时候你想在另外一台电脑或者服务器运行,那么你需要在另外一台电脑或者服务器配置相同的软件,比如数据库,web服务器,必要的插件和库等…

QT学习第一天,创建工程文件,创建按钮,对象树的概念

创建qt 方式一:欢迎》project》new project 方式二:菜单栏》文件》新建文件或项目 打开项目 方式1: 欢迎》project》open project 方式2:打开目录(页面上不存在的项目) 创建工程时需要注意&#xff1…

try langchain (by quqi99)

作者:张华 发表于:2024-03-18 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明(http://blog.csdn.net/quqi99) 基于Gemma的测试环境搭建 想学习一个langchain, 所以先运行一下langchain…

8个国产全能型AI写作神器,给个标题就能自动生成全文 #其他#知识分享

国外ChatGPT爆火,AI写作在国内也引起不小的瞩目,目前国内的AI写作工具少说也有几十上百个,要在这么多AI写作中找出适合自己的工具,一个一个尝试是不太现实的,所以今天就给大家推荐一些款AI写作工具。帮助你少走弯路&am…

【黄啊码】使用cloudflare搭建OpenAI的接口

现在,我们可以使用cloudflare自己搭建一个OpenAI代理服务,使用我们自己的转发代理 第一步:注册cloudflare账号 前往官方网站注册一个账户 第二步:创建worker,进行请求中转 名字可以自己随便取一个,点击快…

GraalVM详细安装及打包springboot、java、javafx使用教程(打包springboot3篇)

前言 在当前多元化开发环境下,Java作为一种广泛应用的编程语言,其应用部署效率与灵活性的重要性日益凸显。Spring Boot框架以其简洁的配置和强大的功能深受开发者喜爱,而JavaFX则为开发者提供了构建丰富桌面客户端应用的能力。然而&#xff…

STC89C52单片机启动--综合案例秒表

代码功能: 1.自动开始计数,一共5个数码管来显示时间。一位数码管显示0-9,对应分度值是0.1s;两位数码管显示00-59,对应分度值1s;两位数码管显示00-59,对应分度值1min;上电后自动开始计…

C# 提示信息的几种方式,总有一种是你不知道的

很久没写文章了,今天有时间就总结一下关于提示信息的几种写法。 (1)第一种提示信息是winform中的提示信息MessageBox.Show() 开发C#的朋友们肯定都用过这个提示信息,这个方法中有几个参数。 平时用的比较多的形式有:…

Springboot笔记-03

1.properties配置文件 #配制oerson的值 person.lastname张三 person.age12 person.birth2017/12/12 person.bossfalse person.dog.namedag person.dog.age15 person.maps.k1v1 person.maps.k212 person.listsa,b,c运行结果乱码 因为idea默认是utf-8编码而properties是ascall编…

奇客PDF评论:优点、缺点和个人的结论

作为一个在职业中接触大量PDF文档的人,PDF编辑器对我来说是基本必需品。我希望我的 PDF 编辑器使用起来非常简单,同时还提供普通的编辑和转换功能。 我对功能足够强大、又不失简单性的 PDF 编辑器的追求最初让我想到了奇客PDF。 在过去的1年里我一直在…

Stompy:一款针对时间戳的Timestomp工具

关于Stompy Stompy是一款功能强大的时间戳管理工具,在该工具的帮助下,广大研究人员能够轻松对指定文件或目录的时间戳进行修改和操作。该工具基于PowerShell开发,并且支持对目标目录中的所有文件执行递归时间戳操作。 功能介绍 1、修改独立…

鸿蒙Harmony应用开发—ArkTS(@Extend装饰器:定义扩展组件样式)

在前文的示例中,可以使用Styles用于样式的扩展,在Styles的基础上,我们提供了Extend,用于扩展原生组件样式。 说明: 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 装饰器使用说明 语法 Extend(UI…

【NC】:追踪 CeO2上 Pt 单位点的形成、归宿和催化活性结果

摘要:铂单中心由于其高原子经济性而非常有吸引力,并且可以通过氧化高温处理在CeO2上生成。然而,它们的位置和活动引起了激烈的争论。此外,迄今为止,反应驱动的结构动力学尚未得到解决。在这项研究中,我们能…

JavaEE 初阶篇-多线程属性和方法

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 创建线程对象并命名 2.0 线程属性 2.1 线程属性 - ID 2.2 线程属性 - 名称 2.3 线程属性 - 后台线程 2.4 线程属性 - 判断 PCB 是否存活 2.5 线程属性 - 终止线程…

Redis实战篇-1

实战篇Redis 开篇导读 短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码…

数据格式化方法

首先你需要一个可以展示代码的组件; 我使用的是tech-ui(内部组件库); 你如果没有类似的组件,可以参考以下链接替代: react-monaco-editor -- 代码编辑器(适用Umi)_umi monaco editor-CSDN博客 Codemirror -- 代码编辑器(react…

Flutter Widget:StatefulWidgetStatelessWidgetState

Widget 概念 Widget 将是构建Flutter应用的基石,在Flutter开发中几乎所有的对象都是一个 Widget 。 在Flutter中的widget 不仅表示UI元素,也表示一些功能性的组件,如:手势 、主题Theme 等。而原生开发中的控件通常只是指UI元素。…

RK3568驱动指南|第十三篇 输入子系统-第145 章 输入子系统上报数据格式分析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工…

CodeSys创建自定义的html5控件

文章目录 背景创建html5control.xml文件控件界面以及逻辑的实现使用的资源安装自定义的html5控件库 背景 查看官方的资料:https://content.helpme-codesys.com/en/CODESYS%20Visualization/_visu_html5_dev.html 官方的例子:https://forge.codesys.com/…