数据结构 —— 线索二叉树

news2024/11/27 16:26:57

数据结构 —— 线索二叉树

  • 线索二叉树
  • 结构定义
    • 结点类
    • 树类
  • 线索化
  • 找线索二叉树的后继
  • 找线索二叉树的前驱

我们今天来看看线索二叉树

线索二叉树

线索二叉树(Threaded Binary Tree)是一种特殊的二叉树结构,它是在二叉树的基础上进行改良的数据结构,主要是为了解决二叉树在空指针上的遍历效率问题。其提出的背景主要基于以下几个方面:

  1. 空指针浪费空间:在传统的二叉树中,每个节点都有两个指针分别指向其左孩子和右孩子。对于叶子节点或者缺失孩子的节点,这些指针会指向NULL,这在大规模数据结构中会浪费大量的存储空间,尤其是当树的深度较大时。
  1. 遍历效率问题:在遍历二叉树时(如前序、中序、后序遍历),需要不断地检查节点的左右孩子是否为空,这增加了算法的时间复杂度。特别是在中序遍历中,需要重复地回到上一层节点,以访问右子树,这种回溯操作降低了遍历的效率。

为了解决这些问题,线索二叉树的概念被提出。在线索二叉树中,将那些空的指针(指向NULL的指针)改用来指向某种顺序下的下一个节点(前驱或后继节点),这样就形成了一种链式的结构,使得遍历更加高效。具体来说:

  • 线索化:将空的左指针指向该节点在某种遍历顺序下的前驱节点,将空的右指针指向后继节点。
  • 标志位:为了区分指针是指向孩子节点还是线索(前驱/后继节点),每个节点通常会增加两个标志位,指示左指针和右指针是否为线索。

通过这种方式,线索二叉树可以在不增加额外存储空间的前提下,实现对二叉树的快速遍历,尤其是在频繁进行中序遍历等操作时,能够显著提高效率。

在这里插入图片描述

结构定义

结点类

我们还是首先把结点类创造出来:

// 定义二叉树结点
template<class T>
class BTreeNode
{
public:
    // 构造函数,初始化结点数据、左孩子和右孩子指针
    BTreeNode(T data)
            : _data(data)
            , _leftchild(nullptr)
            , _rightchild(nullptr)
    {

    }

    // 数据
    T _data;
    // 左右孩子指针
    BTreeNode<T>* _leftchild;
    BTreeNode<T>* _rightchild;

    // 线索化标志,用于标记当前结点的左指针是否为线索
    int lflag = 0;
    // 线索化标志,用于标记当前结点的右指针是否为线索
    int rflag = 0;
};

树类

然后我们根据这个结点,创建一个线索二叉树类,这里创建一棵二叉搜索树:

// 定义线索二叉树
template<class T>
class ThreadBTree
{
public:
    // 构造函数,初始化根结点
    ThreadBTree(T data)
    {
        _root = new BTreeNode<T>(data);
    }

    // 插入结点到二叉树中
    void _Insert(BTreeNode<T>*& root, T data)
    {
        if (root == nullptr)
        {
            root = new BTreeNode<T>(data);
            return;
        }

        // 根据数据大小,将结点插入到左子树或右子树
        if (root->_data< data)
        {
            _Insert(root->_rightchild, data);
        }
        else if (root->_data > data)
        {
            _Insert(root->_leftchild, data);
        }
    }

    // 外部调用接口,插入结点到二叉树中
    void Insert(T data)
    {
        _Insert(_root, data);
    }

    // 中序遍历二叉树
    void _Inorder(BTreeNode<T>* root)
    {
        if (root == nullptr)
        {
            return;
        }

        _Inorder(root->_leftchild);
        //操作
        std::cout << root ->_data << " ";
        _Inorder(root->_rightchild);
    }

    // 外部调用接口,中序遍历二叉树
    void Inorder()
    {
        _Inorder(_root);
    }


private:
    BTreeNode<T>* _root; // 根结点指针
};

这样我们建立好了一棵二叉搜索树,我们插入几个数试试:

#include "ThreadBTree.h"

int main()
{
    ThreadBTree<int> bt(23);

    bt.Insert(44);
    bt.Insert(1);
    bt.Insert(2);
    bt.Insert(29);
    bt.Insert(7);

    bt.Inorder();
    std::cout << std::endl;

    return 0;
}

在这里插入图片描述
构建了像这样的一棵搜索二叉树:
在这里插入图片描述

线索化

现在我们可以对这棵二叉树进行线索化,我们先来看看这棵树有多少的空指针域:
在这里插入图片描述我们这里采用模拟中序,然后线索化,我们定义一个prve指针,记录当前上一步到哪里

但是这里注意,一开始prve为空,可以当做第一个结点的NULL
在这里插入图片描述然后定义一个cur指针,从根节点开始:
在这里插入图片描述
然后cur到了1这里:
在这里插入图片描述
然后prve开始记录cur路径,当cur到2的时候,prve到1
在这里插入图片描述这个时候cur左孩子为空,修改cur左孩子标志位,标志此时左孩子担任线索,并指向prve
在这里插入图片描述依次类推,2和7也是这样,到7这里,左孩子也可以作为线索:
在这里插入图片描述
这样我们可以写出前半段代码:

	// 线索化处理函数
    void vist(BTreeNode<T>* cur)
    {
        // 如果当前结点的左孩子为空,将当前结点的左指针指向前一个结点,并设置线索化标志
        if (cur->_leftchild == nullptr)
        {
            cur->_leftchild = _prve;
            cur->lflag = 1;
       }
        // 更新前一个结点为当前结点
        _prve = cur;
    }

接下来,cur会走到23,prve会走到7:
在这里插入图片描述这个时候cur是prve的后继
在这里插入图片描述
我们写出后半段的代码:

		// 线索化处理函数
    void vist(BTreeNode<T>* cur)
    {
        // 如果当前结点的左孩子为空,将当前结点的左指针指向前一个结点,并设置线索化标志
        if (cur->_leftchild == nullptr)
        {
            cur->_leftchild = _prve;
            cur->lflag = 1;
        }

        // 如果前一个结点的右孩子为空,将前一个结点的右指针指向当前结点,并设置线索化标志
        if (_prve != nullptr && _prve->_rightchild == nullptr)
        {
            _prve->_rightchild = cur;
            _prve->rflag = 1;
        }

        // 更新前一个结点为当前结点
        _prve = cur;
    }

我们改造一下线索二叉树类:

#pragma once
#include<iostream>

// 定义二叉树结点
template<class T>
class BTreeNode
{
public:
    // 构造函数,初始化结点数据、左孩子和右孩子指针
    BTreeNode(T data)
            : _data(data)
            , _leftchild(nullptr)
            , _rightchild(nullptr)
    {

    }

    // 数据
    T _data;
    // 左右孩子指针
    BTreeNode<T>* _leftchild;
    BTreeNode<T>* _rightchild;

    // 线索化标志,用于标记当前结点的左指针是否为线索
    int lflag = 0;
    // 线索化标志,用于标记当前结点的右指针是否为线索
    int rflag = 0;
};

// 定义线索二叉树
template<class T>
class ThreadBTree
{
public:
    // 构造函数,初始化根结点
    ThreadBTree(T data)
    {
        _root = new BTreeNode<T>(data);
    }

    // 插入结点到二叉树中
    void _Insert(BTreeNode<T>*& root, T data)
    {
        if (root == nullptr)
        {
            root = new BTreeNode<T>(data);
            return;
        }

        // 根据数据大小,将结点插入到左子树或右子树
        if (root->_data< data)
        {
            _Insert(root->_rightchild, data);
        }
        else if (root->_data > data)
        {
            _Insert(root->_leftchild, data);
        }
    }

    // 外部调用接口,插入结点到二叉树中
    void Insert(T data)
    {
        _Insert(_root, data);
    }

    // 中序遍历二叉树
    void _Inorder(BTreeNode<T>* root)
    {
        if (root == nullptr)
        {
            return;
        }

        _Inorder(root->_leftchild);
        //vist(root); // 访问当前结点,进行线索化处理
        std::cout << root ->_data << " ";
        _Inorder(root->_rightchild);
    }

    // 外部调用接口,中序遍历二叉树
    void Inorder()
    {
        _Inorder(_root);
    }

    // 线索化处理函数
    void vist(BTreeNode<T>* cur)
    {
        // 如果当前结点的左孩子为空,将当前结点的左指针指向前一个结点,并设置线索化标志
        if (cur->_leftchild == nullptr)
        {
            cur->_leftchild = _prve;
            cur->lflag = 1;
        }

        // 如果前一个结点的右孩子为空,将前一个结点的右指针指向当前结点,并设置线索化标志
        if (_prve != nullptr && _prve->_rightchild == nullptr)
        {
            _prve->_rightchild = cur;
            _prve->rflag = 1;
        }

        // 更新前一个结点为当前结点
        _prve = cur;
    }

private:
    BTreeNode<T>* _root; // 根结点指针
    BTreeNode<T>* _prve = nullptr; // 用于记录中序遍历过程中的前一个结点
    BTreeNode<T>* _cur = _root; // 当前遍历到的结点,默认为根结点
};

找线索二叉树的后继

现在我们有了一棵线索化的二叉树,现在我们想给定一个结点,找它的后继结点:
在这里插入图片描述
如果给定结点的右子树只有一个结点,则后继结点就是这个结点,但是如果右节点的左子树有结点
在这里插入图片描述如果没有结点,则右孩子为线索,直接返回线索:
在这里插入图片描述

    BTreeNode<T>* FisrtNode(BTreeNode<T>* node)
    {
       // 循环向下遍历,直到找到一个左标志为1的节点(表示左孩子是线	索,指向实际节点)
	    while (node->lflag == 0)
	    {
	        node = node->_leftchild;
	    }
    	return node; // 返回第一个实际节点
    }

    BTreeNode<T>* NextNode(BTreeNode<T>* node)
    {
        if (node->rflag == 0)
            return FisrtNode(node->_rightchild);
        else if (node->rflag == 1)
            return node->_rightchild;
    }

    BTreeNode<T>* FindNode(T data)
    {
       return  _FindNode(_root, data);
    }

    BTreeNode<T>* _FindNode(BTreeNode<T>* root, T data)
    {
        if (root->_data == data)
        {
            return root;
        }

        if (root->_data < data)
        {
            return _FindNode(root->_rightchild, data);
        }
        else if (root->_data > data)
        {
            return _FindNode(root->_leftchild, data);
        }

        return nullptr;
    }

找线索二叉树的前驱

找前驱和找后继的逻辑差不多:

    BTreeNode<T>* FisrtNode2(BTreeNode<T>* node)
    {
        while (node->rflag == 0)
        {
            node = node->_rightchild;
        }

        return node;
    }

    BTreeNode<T>* NextNode2(BTreeNode<T>* node)
    {
        if (node->lflag == 0)
            return FisrtNode(node->_leftchild);
        else if (node->lflag == 1)
            return node->_leftchild;
    }

我们来试试:

#include "ThreadBTree.h"

int main()
{
    ThreadBTree<int> bt(23);

    bt.Insert(44);
    bt.Insert(1);
    bt.Insert(2);
    bt.Insert(29);
    bt.Insert(7);

    bt.Inorder();

    BTreeNode<int>* node = bt.FindNode(7);

    BTreeNode<int>* next_node = bt.NextNode(node);

    std::cout << "后继结点为:" << next_node->_data << std::endl;

    BTreeNode<int>* prve_node = bt.NextNode2(node);

    std::cout << "前驱结点为:" << prve_node->_data << std::endl;

    return 0;
}

在这里插入图片描述

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

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

相关文章

前端 CSS 经典:旋转边框效果

效果&#xff1a; 思路&#xff1a;使用伪元素&#xff0c;给伪元素设置背景色&#xff0c;然后定位&#xff0c;遮盖&#xff0c;旋转。就可以实现旋转边框效果。 实现代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta chars…

[FlareOn5]Ultimate Minesweeper

一切题目&#xff0c;可以运行的&#xff0c;首先就要自己运行一次 运行完毕你会发现这是个扫雷游戏 net dnspy打开 一般没有特别的 我们都是点这花括号 这有个getkey 一眼加加密 然后可以三个方向&#xff1a;动调&#xff0c;改文件&#xff0c;静态找数据写加密脚本 简…

软件工程学系统设计

一、概述 软件设计阶段用比较抽象概括的方式确定目标系统如何完成预定的任务&#xff0c;即确定系统的物理模型。 回答系统 “做什么”。 软件设计是将需求转化为最终产品的唯一途径&#xff0c;是后续开发和维护工作的基础。 1、软件设计过程 从工程管理角度&#xff0c;…

STM32通过SPI软件读写W25Q64

文章目录 1. W25Q64 2. 硬件电路 3. W25Q64框架图 4. 软件/硬件波形对比 5. 代码实现 5.1 MyI2C.c 5.2 MyI2C.h 5.3 W25Q64.c 5.4 W25Q64.h 5.5 W25Q64_Ins.h 5.6 main.c 1. W25Q64 对于SPI通信和W25Q64的详细解析可以看下面这篇文章 STM32单片机SPI通信详解-CSDN博…

手撕排序2--选择排序(直接选择+堆排序

目录&#xff1a; 1.直接选择排序 的实现及分析 2.堆排序 的实现及分析 1.直接选择排序 1.1基本思想&#xff1a; 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完…

BP神经网络-入门到理解-长文讲述

本文来自&#xff1a;老饼讲解-BP神经网络 https://www.bbbdata.com 目录 一、BP神经网络的仿生意义 二、BP神经网络的结构 三、BP神经网络的前馈与后馈 3.1 BP神经网络的前馈 3.2 什么是BP神经网络的后馈 四、BP神经网络的训练 4.1 BP神经网络归一化 4.2 梯度下降算法…

Django使用

一、安装Django 可以在创建好的虚拟环境中下载包 pip install Django3.2.20 -i https://pypi.tuna.tsinghua.edu.cn/simple 查看对应的下载好的内容 1、在Lib的site-packages第三方包里&#xff1a;这就是django框架源码 2、在scripts中有个 这是个工具&#xff0c;帮助创建d…

vs2022 studio控制台出现中文乱码解决

vs2022 studio控制台出现中文乱码解决 问题解决 问题 这里cout中间的中文&#xff0c;但控制台出现的是乱码对此需要进行修改 解决 打开运行的主文件&#xff0c;也就是整个程序的入口&#xff0c;对他另存为 之后点击编码保存 接着将编码保存的格式变为图片对应的这种 记…

微信小程序UI组件库合集

文章目录 前言参考地址推荐组件库1.官方WeUI&#xff08;建议使用☆☆☆☆&#xff09;2.ColorUI&#xff08;广告很多&#xff0c;不建议使用&#xff09;3.vantUI又名&#xff1a;ZanUI&#xff08;操作简单&#xff0c;建议使用☆☆☆☆&#xff09;4.MinUI&#xff08;比较…

STM32通过SPI硬件读写W25Q64

文章目录 1. W25Q64 2. 硬件电路 3. 软件/硬件波形对比 4. STM32中的SPI外设 5. 代码实现 5.1 MyI2C.c 5.2 MyI2C.h 5.3 W25Q64.c 5.4 W25Q64.h 5.5 W25Q64_Ins.h 5.6 main.c 1. W25Q64 对于SPI通信和W25Q64的详细解析可以看下面这篇文章 STM32单片机SPI通信详解-C…

设计模式4-模版方法

设计模式 重构获得模式重构的关键技法1. 静态转动态2. 早绑定转晚绑定3. 继承转组合4. 编译时依赖转运行时依赖5. 紧耦合转松耦合 组件协助动机模式定义结构 要点总结。 例子示例解释&#xff1a; 重构获得模式 设计模式的目的是应对变化&#xff0c;提高复用 设计模式的要点…

昇思25天学习打卡营第3天|数据集Dataset

一、简介&#xff1a; 数据是深度学习的基础&#xff0c;高质量的数据输入将在整个深度神经网络中起到积极作用。有一种说法是模型最终训练的结果&#xff0c;10%受到算法影响&#xff0c;剩下的90%都是由训练的数据质量决定。&#xff08;doge&#xff09; MindSpore提供基于…

基于STM32的智能病房监控和人脸识别系统设计(毕业设计)

摘 要 随着技术的不断进步和医疗需求的不断增长&#xff0c;智能病房控制系统有望在医疗领域发挥更大的作用。基于此&#xff0c;本文研究设计了一款低成本、操作简单、适用性强的基于STM32的智能病房监控和人脸识别系统。该系统通过STM32作为控制器和OpenMV对人脸分辨进行门…

你好,复变函数1.0

输入时用后缀&#xff0c;开头空格 #include <easyx.h> #include <stdio.h> #define PI 3.141592653589793 #define E 2.718281828459045 #define K (1.0 / 256.0) #define K_1 256.0 //#define LINE//决定函数是用线画还是用点画 struct C {double i;double r;…

同一天里,两位大厂程序员猝死。。。

2024年&#xff0c;真的不是平静的一年。在几天前&#xff0c;IT行业接连发生了两件不幸的事情。 6月17日下午&#xff0c;东南亚电商公司Sh**ee位于北京的研发中心&#xff0c;一位负责研发的女员工突然在工位上晕倒。 同事们赶紧拨打了120&#xff0c;然而还是没能抢救过来&a…

USB2.0网卡安装驱动

有三种安装方式&#xff1a; 驱动精灵驱动总裁USB2.0网卡自带安装程序 前两种很简单&#xff0c;下载驱动精灵或者驱动总裁&#xff0c;然后检测本地硬件&#xff0c;安装相应驱动。 本文重点要介绍的是第三种&#xff0c;利用USB2.0网卡自带的安装程序。有的时候驱动精灵或…

高考志愿填报,如何避免报错专业?

高考志愿填报绝对是关键一环节&#xff0c;分数高低暂且不论&#xff0c;因为这个填报志愿&#xff0c;大概率是决定了余生的职业&#xff0c;也有人说&#xff0c;大学可以转专业&#xff0c;毕业还可以跨行就业&#xff0c;工作了还可以转行.....确实有这个可能性&#xff0c…

如何生成protobuf文件

背景 protobuf是一种用于序列化结构数据的工具&#xff0c;实现数据的存储与交换&#xff0c;与编程语言和开发平台无关。 序列化&#xff1a;将结构数据或者对象转换成能够用于存储和传输的格式。 反序列化&#xff1a;在其他的计算环境中&#xff0c;将序列化后的数据还原为…

【React】AntD组件---极客园--01.项目前置准备

项目搭建 基于CRA创建项目 CRA是一个底层基于webpack快速创建React项目的脚手架工具 # 使用npx创建项目 npx create-react-app react-jike# 进入到项 cd react-jike# 启动项目 npm start调整项目目录结构 -src-apis 项目接口函数-assets 项目资源文件&…

【鸿蒙】创建第⼀个鸿蒙项⽬

点击 Create Project 配置项目 开发工具界面 工程介绍