【数据结构-零基础学习】线索二叉树(代码+图示+解析)

news2024/9/23 13:20:30

【数据结构-零基础学习】线索二叉树(代码+图示+解析)

文章目录

  • 【数据结构-零基础学习】线索二叉树(代码+图示+解析)
    • @[toc]
      • 定义
      • 产生背景
      • 种类
      • 示意图
        • 1)未加入线索的普通二叉树示意图1.1
        • 2)线索添加的规则
        • 3)中序线索二叉树示意图1.2
        • 4)中序线索二叉树分析示意图1.3
      • 设计代码逻辑(重点)
      • 代码实现
          • ThreadedBinaryTree类
          • Test测试类
          • 测试结果
      • 优势

定义

​ 线索二叉树是一种二叉树的数据结构,它的特点在于空闲指针用于指向节点在某种特定遍历方式下的前驱或后继。在传统的二叉树中,每个节点有两个指针,指向其左孩子和右孩子。如果任一孩子不存在,相应的指针便为空。线索二叉树利用这些空指针,存储指向遍历序列中前驱或后继的指针,从而增加遍历效率。


产生背景

  • 线索二叉树产生的原因主要是为了提高二叉树的遍历效率。在未线索化的二叉树中,遍历元素(特别是非递归遍历)通常需要借助栈或者使用递归,这样会造成一定的空间和时间上的开销。线索二叉树通过利用空闲的指针字段,使得在遍历过程中能够直接找到节点的前驱和后继,从而避免了栈或递归的使用,减少了计算量和存储空间的需求。

  • 将非线性的二叉树结构转化成线性结构的一种方式是通过遍历,如果使用链式存储二叉树的时候,我们往往只能通过遍历的手段获得某个节点的前驱和后继,这是因为使用链式存储二叉树的时候会发现节点有两个指针域,一个是指向左孩子,一个指向右孩子.

  • 我们如何在非线性结构的二叉树中直接找到线性结构中的节点的前驱和后继节点的信息呢?

    • 我们现在可以获取的关于遍历序列的线性结构的二叉树的关于节点的前驱和后继节点的信息只有根节点和叶子节点,即 除根节点外每个节点都有且只有一个直接前驱节点,除叶子节点外的每个节点都有且只有一个后继节点,这是分析线性结构即遍历序列可以知道的信息)
  • 那么我们如何通过在非线性结构的二叉树中直接找到线性结构中所有节点的直接前驱节点和后继节点呢 ?

在这里插入图片描述


种类

线索二叉树中的“线索”是根据二叉树的遍历顺序添加的,所以前序、中序和后序线索二叉树在相同的二叉树结构上会有不同的线索指向。

  1. 中序线索二叉树:

​ 在这种类型的线索二叉树中,线索指向的是中序遍历的前驱和后继。也就是说,一个节点的左线索指向它的中序前驱,右线索指向它的中序后继。

  1. 前序线索二叉树:

在前序线索二叉树中,线索指向的是前序遍历的前驱和后继。因此,一个节点的左线索会指向它的前序前驱(如果存在),而右线索指向它的前序后继。

  1. 后序线索二叉树:

与前两者类似,后序线索二叉树中的线索指向的是后序遍历的前驱和后继。

  • 由于前序、中序、后序遍历的访问顺序不同,因此即使是相同的二叉树,在不同类型的线索二叉树中,节点的线索指向确实是不同的。这也意味着,为同一个二叉树构建前序、中序或后序线索二叉树时,需要进行不同的处理逻辑,以确保线索正确地指向正确的前驱或后继节点。

​ 我们在这里讲解的是 " 中序遍历二叉树的情况下给普通二叉树加入线索后构建中序线索二叉树 " !


示意图

1)未加入线索的普通二叉树示意图1.1

在这里插入图片描述

分析

上述的二叉树一共有n = 7 个节点,那么我们给这个二叉树的每个节点都添加了两个引用,那么我们可以知道 二叉树中引用一共有 2n= 14 个, 有n-1 = 6 个引用是被占用了,那么就还剩下 2n - (n-1) = n + 1 = 8 个空闲的引用.
那么我们如果把这空闲出来的当做线索,也就是我们刚刚改造普通二叉树变成线索二叉树的讲解的方法二,我们就可以知道把这 8 个引用利用起来,分别指向当前节点的直接前驱节点或直接后继节点,这样就完成了线索的添加.

而且我们还可以利用线性结构的二叉树遍历结果知道 :
除第一个节点无直接前驱节点已经最后一个节点无直接后继节点外其余节点一定都有直接前驱节点和直接后继节点.所以 8 个引用中会有两个引用是空的,方别是第一个节点和最后一个节点.

<所以现在我们可以中序遍历给每个节点都添加上对应的前驱和后继节点>


2)线索添加的规则
  1. 对于每个节点:
    • 如果节点的左指针为空,则将左指针指向其中序遍历的前驱节点,并标记左指针为线索。
    • 如果节点的右指针为空,则将右指针指向其中序遍历的后继节点,并标记右指针为线索。
  2. 遍历过程:
    • 从根节点开始,递归地对左子树进行中序遍历并线索化。
    • 访问当前节点,设置线索。
    • 递归地对右子树进行中序遍历并线索化。
3)中序线索二叉树示意图1.2

在这里插入图片描述

分析

的确,我们通过中序遍历这个二叉树的同时添加上线索之后,这个二叉树就叫做 线索二叉树


4)中序线索二叉树分析示意图1.3

在这里插入图片描述


设计代码逻辑(重点)

我在这里先提前设计一下实现代码的思路,这样可以理清代码的逻辑.

  • 代码的逻辑和实现框架概述:
  1. 类结构:
    • ThreadedBinaryTree: 主类,用于实现线索二叉树。
    • BinaryTreeNode: 嵌套静态类,表示二叉树的节点。
    • DataIllegalException: 自定义异常类,用于处理非法数据。
  2. 主要属性:
    • root: 二叉树的根节点。
    • pre: 用于在线索化过程中保存前一个节点。
    • size: 节点的数量。
  3. 构造方法:
    • 无参构造器初始化线索二叉树。
    • 带有根节点参数的构造器,用于创建带有根节点的线索二叉树。
  4. 核心方法:
    • insertNode(): 向线索二叉树中插入新节点。
    • inorderThreaded(): 对二叉树进行中序线索化。
    • inThreadList(): 中序遍历线索化后的二叉树。
  5. 辅助方法:
    • isEmpty(): 检查树是否为空。
    • size(): 返回树的大小。
    • getRoot(): 获取树的根节点。
    • getLeft(), getRight(): 分别获取节点的左孩子和右孩子。
    • checkParent(): 检查父节点是否为空。
    • checkRootDataIllegal(): 检查节点数据是否非法。

代码实现

ThreadedBinaryTree类
package src.test.java;

/**
 * 线索二叉树测试Threaded Binary Tree
 * 这个测试类目的是用来实现线索二叉树的(中序)
 */
public class ThreadedBinaryTree<E> {


    private BinaryTreeNode<E> root = null;//根节点
    private BinaryTreeNode<E> pre = null; // 用于保存前一个节点
    private int size;//节点个数

    /**
     *无参构造方法,可以完成部分属性的初始化
     */
    public ThreadedBinaryTree(){
    }

    /**
     *
     * @param root
     */
    public ThreadedBinaryTree(BinaryTreeNode<E> root){
        try {
            checkRootDataIllegal(root.val);
        } catch (DataIllegalException e) {
            throw new RuntimeException(e);
        }
        this.root = root;
        size++;
    }

    /**
     * 判空
     * @return 如果二叉树为空则返回ture
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 检查数据是否为非法的数据
     * @param data val值
     * @throws DataIllegalException 自定义的异常(当数据为空的时候抛出该异常,并打印信息 "数据不能存储空值")
     */
    private void checkRootDataIllegal(E data) throws DataIllegalException {
        if (data==null){
        throw new DataIllegalException("数据不能存储空值");
        }
    }

    /**
     * 节点个数
     * @return 返回的二叉树节点的个数
     */
    public int size(){
        return size;
    }

    /**
     * 获取搜索二叉树的根节点
     * @return 根节点对象
     */
    public BinaryTreeNode<E> getRoot(){
        return this.root;
    }

    /**
     * 获取左孩子
     * @param parent 父亲节点
     * @return 返回的当前父亲节点的左孩子
     */
    public BinaryTreeNode<E> getLeft(BinaryTreeNode<E> parent){
        checkParent(parent);
        //注意需要判断传进来的父亲节点对象是否存在左孩子,如果存在才可以返回左孩子,如果不存在就返回null
        return parent.left;
    }

    /**
     * 获取右孩子
     * @param parent 父亲节点
     * @return 返回当前父亲节点的右孩子
     */
    public BinaryTreeNode<E> getRight(BinaryTreeNode<E> parent){
        //注意需要判断传进来的父亲节点是否存在右孩子,如果存在才可以返回右孩子对象,如果不存在就返回null
        return parent.right;
    }

    /**
     * 检查父亲节点是否为空,如果为空的话就需要抛出异常
     * @param parent 传进来的父亲节点
     */
    private void checkParent(BinaryTreeNode<E> parent){
        if(parent ==null){
            throw  new NullPointerException("父亲节点不能为空");
        }
    }


    /**
     * 向线索二叉树中插入节点
     * @param parent 父节点
     * @param child 要插入的新节点
     * @param isLeft 如果为true,则将节点插入为左孩子;如果为false,则为右孩子
     */
    public void insertNode(BinaryTreeNode<E> parent, BinaryTreeNode<E> child, boolean isLeft) {
        if (parent == null) {
            if (root == null) {
                root = child; // 如果根节点为空,则新节点成为根节点
            } else {
                throw new IllegalStateException("不能在空的父节点上插入");
            }
        } else {
            if (isLeft && parent.left == null) {
                parent.left = child;
            } else if (!isLeft && parent.right == null) {
                parent.right = child;
            } else {
                throw new IllegalStateException("指定的位置已有节点");
            }
        }
        size++;
    }


    // 线索遍历函数
    // 实现中序遍历的同时线索化
    public void inorderThreaded() {
        inorderThreaded(this.root);
    }

    private void inorderThreaded(BinaryTreeNode<E> node) {
        if (node == null) {
            return;
        }

        // 遍历左子树
        inorderThreaded(node.left);

        // 处理当前节点的前驱
        if (node.left == null) {
            node.left = pre;
            node.isLeftThread = true;
        }

        // 处理前一个节点的后继
        if (pre != null && pre.right == null) {
            pre.right = node;
            pre.isRightThread = true;
        }

        pre = node; // 更新前一个节点

        // 遍历右子树
        inorderThreaded(node.right);
    }


    /**
     * 中序遍历线索二叉树
     */
    public void inThreadList(BinaryTreeNode<E> root) {
        if (root == null) {
            return;
        }
        //查找中序遍历的起始节点
        while (root != null && !root.isLeftThread) {
            root = root.left;
        }

        while (root != null) {
            System.out.print(root.val + " ");
            // 如果右子节点是线索
            if (root.isRightThread) {
                root = root.right;
            } else {
                //有右子节点,遍历右子节点
                root = root.right;
                //如果右子节点不为null,并且右子节点的左子结点存在
                while (root != null && !root.isLeftThread) {
                    root = root.left;
                }
            }
        }

    }

    /**
     * 封装的节点类
     * @param <E>
     */
  static   class BinaryTreeNode<E> {
        //数据域
        public E val;
        //保存左孩子或者直接前驱节点的引用(保存直接前驱节点的条件是没有左孩子,如果有左孩子就直接指向左孩子)
        public BinaryTreeNode<E> left;
        //保存右孩子或者直接后继节点的引用(保存直接后继节点的条件是没有右孩子,如果有右孩子就直接指向右孩子)
        public BinaryTreeNode<E> right;

        public boolean isLeftThread;  // 左指针是否为线索
        public boolean isRightThread; // 右指针是否为线索

        /**
         * 构造方法
         *
         * @param val 创建节点的时候就初始化data的数值
         */
        public BinaryTreeNode(E val) {
            this.val = val;
            this.left = null;
            this.right = null;
            this.isLeftThread = false;
            this.isRightThread = false;
        }

        /**
         * 重写ToString()方法
         *
         * @return
         */
        public String toString() {
            return this.val.toString();
        }

    }

}

class DataIllegalException extends Exception{
    public DataIllegalException(String m){
        super(m);
    }
}
Test测试类
package src.test.java;

public class ThreadedBinaryTreeTest {
    public static void main(String[] args) {
        ThreadedBinaryTree<Integer> tree = new ThreadedBinaryTree<>();

        // 创建节点
        ThreadedBinaryTree.BinaryTreeNode<Integer> node6 = new ThreadedBinaryTree.BinaryTreeNode<>(6);
        //添加r根节点的左子结点node3
        ThreadedBinaryTree.BinaryTreeNode<Integer> node3 = new ThreadedBinaryTree.BinaryTreeNode<>(3);
        //添加r根节点的右子结点node7
        ThreadedBinaryTree.BinaryTreeNode<Integer> node7 = new ThreadedBinaryTree.BinaryTreeNode<>(7);
        //添加a节点的左子结点node1
        ThreadedBinaryTree.BinaryTreeNode<Integer> node1 = new ThreadedBinaryTree.BinaryTreeNode<>(1);
        //添加a节点的右子结点node4
        ThreadedBinaryTree.BinaryTreeNode<Integer> node4 = new ThreadedBinaryTree.BinaryTreeNode<>(4);
        //添加b节点的右子结点node2
        ThreadedBinaryTree.BinaryTreeNode<Integer> node2 = new ThreadedBinaryTree.BinaryTreeNode<>(2);
        //添加c节点的左子结点node5
        ThreadedBinaryTree.BinaryTreeNode<Integer> node5 = new ThreadedBinaryTree.BinaryTreeNode<>(5);




        // 插入节点
        tree.insertNode(null, node6, true);
        tree.insertNode(node6, node3,true);
        tree.insertNode(node6, node7, false);
        tree.insertNode(node3, node1, true);
        tree.insertNode(node3, node4, false);
        tree.insertNode(node1, null,true);
        tree.insertNode(node1, node2,false);
        tree.insertNode(node4, null, true);
        tree.insertNode(node4, node5, false);
        tree.insertNode(node2, null, false);
        tree.insertNode(node2, null, true);
        tree.insertNode(node5, null, true);
        tree.insertNode(node5, null, false);
        tree.insertNode(node7, null, true);

        // 线索化
        tree.inorderThreaded();

       //遍历二叉树
        tree.inThreadList(tree.getRoot());
    }
}
测试结果

在这里插入图片描述


优势

线索二叉树在特定的应用场景下提供了明显的优势,尤其是在空间利用率和遍历效率方面。然而,它并不适合所有场景,尤其是在频繁插入和删除操作的动态二叉树中,维护线索会带来额外的复杂性和开销。

  • 遍历效率提高

    线索二叉树允许无需递归或栈即可进行中序、前序或后序遍历,特别是中序遍历,它可以线性时间内完成,即O(n)。

  • 空间利用率提升

    传统的二叉树中大量空闲的指针空间得到了有效利用。

  • 遍历过程简化

    不需要维护复杂的栈结构或者进行递归调用,简化了遍历算法的实现。

  • 方便前驱和后继的查找

    对于某些特定的算法或数据结构,如线性化二叉树或转换为双向链表,线索二叉树提供了便捷的支持。


在这里插入图片描述

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

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

相关文章

户外耳机推荐,这几款高性价比的户外耳机不容错过!

对于喜欢户外运动的人来说&#xff0c;在耳机的选择上至关重要&#xff0c;推荐使用骨传导运动耳机的&#xff0c;因为相较于普通的入耳式耳机&#xff0c;前者是通过振动来传输声音的&#xff0c;而后者则是通过空气传导到耳内的。如果是在户外跑步听不到周围环境声音和鸣笛声…

振南技术干货集:制冷设备大型IoT监测项目研发纪实(2)

注解目录 1.制冷设备的监测迫在眉睫 1.1 冷食的利润贡献 1.2 冷设监测系统的困难 &#xff08;制冷设备对于便利店为何如何重要&#xff1f;了解一下你所不知道的便利店和新零售行业。关于电力线载波通信的论战。&#xff09; 2、电路设计 2.1 防护电路 2.1.1 强电防护 …

vue超好用的自定义指令封装

一、指令封装 目录结构&#xff1a; index.ts 统一注册 import { App, Directive } from "vue"; import auth from "./modules/auth"; import copy from "./modules/copy"; import waterMarker from "./modules/waterMarker"; impor…

Python简直是万能的,这5大主要用途你一定要知道!

从2015开始国内就开始慢慢接触Python了&#xff0c;从16年开始Python就已经在国内的热度更高了&#xff0c;目前也可以算的上"全民Python"了。 众所周知小学生的教材里面已经有Python了&#xff0c;国家二级计算机证也需要学习Python了&#xff01; 因为Python简单…

基于springboot实现冬奥会科普平台系统【项目源码+论文说明】计算机毕业设计

基于SpringBoot实现冬奥会科普平台系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理平台应运而生&…

CSS实现空心的“尖角”

大家好&#xff0c;我是南宫&#xff0c;来分享一个昨天解决的问题。 我记得之前刷面试题的时候&#xff0c;CSS面试题里面赫然有一题是“如何用CSS实现三角形”&#xff0c;我觉得这个问题确实很经典&#xff0c;我上的前端培训班当初就讲过。 大概思路如下&#xff1a; 先…

Redis -- 介绍

1、NoSQL: 指的是非关系型数据库&#xff0c;主要分成四大类&#xff1a;键值存储数据库、列存储数据库、文档型数据库、图形数据库。 2、什么是Redis&#xff1a; Redis是一种基于内存的数据库&#xff0c;一般用于做缓存的中间件。 3、Redis的主要的特点&#xff1a; 1、Rd…

埃尔米特插值(hermite 插值) C++

埃尔米特插值 原理 #pragma once #include <vector> #include <functional> /*埃尔米特插值*/ struct InterpolationPoint {double x; // 插值点的横坐标double y; // 插值点的纵坐标double derivative; // 插值点的导数值// 默认构造函数InterpolationPoint() : x…

一个测试驱动的Spring Boot应用程序开发

文章目录 系统任务用户故事搭建开发环境Web应用的框架Spring Boot 自动配置三层架构领域建模域定义与领域驱动设计领域类 业务逻辑功能随机的Challenge验证 表示层RESTSpring Boot和REST API设计API第一个控制器序列化的工作方式使用Spring Boot测试控制器 小结 这里采用面向需…

str转wstr的三种方法和从网站获取json数据到数据随机提取,返回拼接字符串和动态数组

库的设置 hv库 外部包含目录&#xff1a;…\include\libhv_new\hv; 库目录&#xff1a;…\include\libhv_new\lib\x86\Release; 附加依赖项&#xff1a;hv.lib; //Get请求 获取json数据&#xff0c;然后提取符合 条件的&#xff0c;time值大于自定义变量的值&#xff0c;然后取…

老知识复盘-SQL从提交到执行到底经历了什么 | 京东云技术团队

一、什么是SQL sql(Structured Query Language: 结构化查询语言)是高级的费过程化编程语言,允许用户在高层数据结构上工作, 是一种数据查询和程序设计语言, 也是(ANSI)的一项标准的计算机语言. but… 目前仍然存在着许多不同版本的sql语言,为了与ANSI标准相兼容, 它们必须以相…

webpack 创建typescript项目

【视频链接】尚硅谷TypeScript教程&#xff08;李立超老师TS新课&#xff09; 创建webpack 项目 IDE&#xff1a;webstorm 新建一个空的项目运行npm init初始化项目目录结构 1. 安装 webpack&#xff1a;构建工具webpack-cli&#xff1a; webpack的命令行工具typescript&am…

处理无线debug问题

无限debug的产生 条件说明 开发者工具是打开状态 js代码中有debugger js有定时处理 setInterval(() > {(function (a) {return (function (a) {return (Function(Function(arguments[0]" a ")()))})(a)})(bugger)(de, 0, 0, (0, 0)); }, 1000); ​ #这里就…

【论文阅读】An Experimental Survey of Missing Data Imputation Algorithms

论文地址&#xff1a;An Experimental Survey of Missing Data Imputation Algorithms | IEEE Journals & Magazine | IEEE Xplore 处理缺失数据最简单的方法就是是丢弃缺失值的样本&#xff0c;但这会使得数据更加不完整并且导致偏差或影响结果的代表性。因此&#xff0c;…

wpf使用CefSharp.OffScreen模拟网页登录,并获取身份cookie

目录 框架信息&#xff1a;MainWindow.xamlMainWindow.xaml.cs爬取逻辑模拟登录拦截请求Cookie获取 CookieVisitorHandle 框架信息&#xff1a; CefSharp.OffScreen.NETCore 119.1.20 MainWindow.xaml <Window x:Class"Wpf_CHZC_Img_Identy_ApiDataGet.MainWindow&qu…

虚函数可不可以重载为内联 —— 在开启最大优化时gcc、clang和msvc的表现

下面是对该问题的一种常见回答&#xff1a; 首先&#xff0c;内联是程序员对编译器的一种建议&#xff0c;因此可以在在重载虚函数时在声明处加上inline关键字来修饰&#xff0c; 但是因为虚函数在运行时通过虚函数表&#xff0c;而内联函数在编译时进行代码嵌入&#xff0c;因…

【Spring】之IoC与对象存取

未来的几周时间&#xff0c;大概率我会更新一下Spring家族的一些简单知识。而什么是Spring家族&#xff0c;好多同学还不是很清楚&#xff0c;我先来简单介绍一下吧&#xff1a; 所谓Spring家族&#xff0c;它其实就是一个框架&#xff0c;是基于Servlet再次进行封装的内容。为…

SpringBoot启动后自动打开浏览器访问项目

更简单的一个方法 Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " url); Springboot项目启动后自动打开浏览器访问(超实用)_浏览器访问springboot项目-CSDN博客 Springboot项目启动后自动打开浏览器访问 1、在Springboot项目中每次启动完项…

DeepWalk: Online Learning of Social Representations(2014 ACM SIGKDD)

DeepWalk: Online Learning of Social Representations----《DeepWalk&#xff1a;用于图节点嵌入的在线机器学习算法》 DeepWalk 是将 word2vector 用到 GNN 上 DeepWalk&#xff1a; 将 Graph 的每个节点编码为一个 D 维向量&#xff08;无监督学习&#xff09;&#xff0c;E…

云HIS系统源码,医院管理系信息统源码,融合B/S版四级电子病历系统

医院管理信息系统是以推进公共卫生、医疗、医保、药品、财务监管信息化建设为着力点&#xff0c;整合资源&#xff0c;加强信息标准化和公共服务信息平台建设&#xff0c;逐步实现统一高效、互联互通的管理系统。 SaaS模式Java版云HIS系统&#xff0c;在公立二甲医院应用三年…