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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【数据结构-零基础学习】线索二叉树(代码+图示+解析)

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


定义

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


产生背景

  • 线索二叉树产生的原因主要是为了提高二叉树的遍历效率。在未线索化的二叉树中,遍历元素(特别是非递归遍历)通常需要借助栈或者使用递归,这样会造成一定的空间和时间上的开销。线索二叉树通过利用空闲的指针字段,使得在遍历过程中能够直接找到节点的前驱和后继,从而避免了栈或递归的使用,减少了计算量和存储空间的需求。
  • 将非线性的二叉树结构转化成线性结构的一种方式是通过遍历,如果使用链式存储二叉树的时候,我们往往只能通过遍历的手段获得某个节点的前驱和后继,这是因为使用链式存储二叉树的时候会发现节点有两个指针域,一个是指向左孩子,一个指向右孩子.
  • 我们如何在非线性结构的二叉树中直接找到线性结构中的节点的前驱和后继节点的信息呢?
  • 我们现在可以获取的关于遍历序列的线性结构的二叉树的关于节点的前驱和后继节点的信息只有根节点和叶子节点,即 除根节点外每个节点都有且只有一个直接前驱节点,除叶子节点外的每个节点都有且只有一个后继节点,这是分析线性结构即遍历序列可以知道的信息)
  • 那么我们如何通过在非线性结构的二叉树中直接找到线性结构中所有节点的直接前驱节点和后继节点呢 ?


种类

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

  1. 中序线索二叉树:

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

  1. 前序线索二叉树:

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

  1. 后序线索二叉树:

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

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

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


示意图

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

分析

上述的二叉树一共有n = 7 个节点,那么我们给这个二叉树的每个节点都添加了两个引用,那么我们可以知道 二叉树中引用一共有 2n= 14 个, 有n-1 = 6 个引用是被占用了,那么就还剩下 2n - (n-1) = n + 1 = 8 个空闲的引用.

那么我们如果把这空闲出来的当做线索,也就是我们刚刚改造普通二叉树变成线索二叉树的讲解的方法二,我们就可以知道把这 8 个引用利用起来,分别指向当前节点的直接前驱节点或直接后继节点,这样就完成了线索的添加.

而且我们还可以利用线性结构的二叉树遍历结果知道 :

除第一个节点无直接前驱节点已经最后一个节点无直接后继节点外其余节点一定都有直接前驱节点和直接后继节点.所以 8 个引用中会有两个引用是空的,方别是第一个节点和最后一个节点.

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


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

分析

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


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


设计代码逻辑(重点)

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

  • 代码的逻辑和实现框架概述:
  1. 类结构:
  • ThreadedBinaryTree: 主类,用于实现线索二叉树。
  • BinaryTreeNode: 嵌套静态类,表示二叉树的节点。
  • DataIllegalException: 自定义异常类,用于处理非法数据。
  1. 主要属性:
  • root: 二叉树的根节点。
  • pre: 用于在线索化过程中保存前一个节点。
  • size: 节点的数量。
  1. 构造方法:
  • 无参构造器初始化线索二叉树。
  • 带有根节点参数的构造器,用于创建带有根节点的线索二叉树。
  1. 核心方法:
  • insertNode(): 向线索二叉树中插入新节点。
  • inorderThreaded(): 对二叉树进行中序线索化。
  • inThreadList(): 中序遍历线索化后的二叉树。
  1. 辅助方法:
  • 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)。
  • 空间利用率提升
    传统的二叉树中大量空闲的指针空间得到了有效利用。
  • 遍历过程简化
    不需要维护复杂的栈结构或者进行递归调用,简化了遍历算法的实现。
  • 方便前驱和后继的查找
    对于某些特定的算法或数据结构,如线性化二叉树或转换为双向链表,线索二叉树提供了便捷的支持。

目录
相关文章
|
22天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
137 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
5天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
31 12
|
5天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
31 10
|
5天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
23 2
|
19天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
1月前
|
PHP 开发者 容器
PHP命名空间深度解析:避免命名冲突与提升代码组织####
本文深入探讨了PHP中命名空间的概念、用途及最佳实践,揭示其在解决全局命名冲突、提高代码可维护性方面的重要性。通过生动实例和详尽分析,本文将帮助开发者有效利用命名空间来优化大型项目结构,确保代码的清晰与高效。 ####
33 1
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
103 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
90 0
|
20天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
20天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多