当前位置: 首页 > backend >正文

线索二叉树

一 概念

线索二叉树(Threaded Binary Tree)是一种对二叉树的优化结构,主要解决传统二叉树遍历时需要借助栈或递归(额外空间开销)的问题。通过利用节点中的空指针(nullptr)存储遍历过程中的前驱或后继节点信息(称为 “线索”),可以实现无需额外空间的二叉树遍历

传统二叉树中,每个节点有两个指针(左孩子lchild、右孩子rchild)。对于一棵有n个节点的二叉树,共有2n个指针域,但其中只有n-1个指针被有效使用(连接子节点),剩余n+1个指针为空(nullptr)。
线索二叉树的核心思想是:将这些空指针利用起来,存储节点在某种遍历顺序(如中序、前序、后序)中的前驱或后继节点的地址,这些被重新利用的指针称为 “线索”。

为了区分一个指针是指向子节点还是线索,每个节点需要增加两个标志位:

  • ltag:左标志位
    • ltag = 0lchild指向左子节点
    • ltag = 1lchild是前驱线索(指向当前节点在遍历顺序中的前驱)
  • rtag:右标志位
    • rtag = 0rchild指向右子节点
    • rtag = 1rchild是后继线索(指向当前节点在遍历顺序中的后继)

二 代码 

1.线索二叉树结点

//线索二叉树结点
struct TreeNode {int data;TreeNode* left;TreeNode* right;bool ltag; //左线索,若为true,则left表示前驱结点,否则为左孩子结点bool rtag; //右线索,若为true,则right表示前驱结点,否则为右孩子结点TreeNode(int d) : data(d), left(nullptr), right(nullptr), ltag(false), rtag(false) {}
};

新增左右线索标志。若左线索为true,则left表示前驱结点,否则为左孩子结点。若右线索为true,则right表示后继结点,否则为右孩子结点。

2.构建线索二叉树

//构建线索二叉树(通过前序遍历序列构建)
TreeNode* CreateTree(vector<int>& preorder, int& index, int nullnode) {//若索引超出数组范围或数组索引处为空值nullnode,则令index++并返回空if (index >= preorder.size() || preorder[index] == nullnode) {index++;return nullptr;}TreeNode* node = new TreeNode(preorder[index++]); //为新结点分配内存node->left = CreateTree(preorder, index, nullnode); //递归构建左子树node->right = CreateTree(preorder, index, nullnode); //递归构建右子树return node; //返回当前结点
}

通过前序遍历序列来构建线索二叉树,若传入的索引超出数组范围或数组索引处为空值nullnode,则令Index++并返回空。为新结点分配内存,然后递归构建左右子树。

3.中序遍历线索化

//中序遍历线索化
TreeNode* pre = nullptr; //定义前驱结点void inorderThreading(TreeNode* node) {if (node == nullptr) return; //当传入结点为空时直接返回inorderThreading(node->left); //递归左子树,找到最左边孩子结点//传入结点的左子结点为空时,令其左指针指向前驱结点preif (node->left == nullptr) { node->left = pre;node->ltag = true;}//当前驱结点不为空且其右子结点为空时,令前驱结点的右指针指向当前结点if (pre && pre->right == nullptr) { pre->right = node;pre->rtag = true;}pre = node; //令前驱结点指向当前结点inorderThreading(node->right); //递归右子树
}

中序遍历线索化是最常用的线索化方式。首先在函数外定义一个全局结点变量pre表示前驱结点。

在函数体中,当传入的结点为空时直接返回。先递归左子树找到最左边孩子结点,当传入结点的左子结点为空时,令其左指针指向前驱结点pre,当前驱结点不为空且右子结点为空时,令前驱结点的右指针指向当前结点。最后令前驱结点指向当前结点并递归右子树。

4.中序遍历线索二叉树

//中序遍历线索二叉树
void inorderTraversal(TreeNode* root) {TreeNode* p = root;//找到中序遍历第一个节点(最左叶子)while (!p->ltag) { // 左线索为false表示有左子树,继续往左找p = p->left;}//循环遍历所有节点while (p != nullptr) {cout << p->data; //访问当前结点//找后继结点if (p->rtag) { //若rtag为true,则直接通过右线索找到后继结点p = p->right;}else { //若rtag为false,则找右子树的最左子结点p = p->right;while (!p->ltag) {p = p->left;}}}
}

首先找到中序遍历的第一个结点(最左叶子结点),左线索为false表示有左子树,继续往左找。

找到后循环遍历所有结点,每次循环先访问当前结点,然后找后继结点。若rtag为true,则直接通过右线索找到后继结点,若rtag为false,则找右子树的最左子结点。

5.释放内存

//释放线索二叉树内存
void FreeTree(TreeNode* root) {if (root == nullptr) return; // 空树直接返回// 保存当前节点的左右真实子节点(仅当标志为false时有效)TreeNode* leftChild = (root->ltag == false) ? root->left : nullptr;TreeNode* rightChild = (root->rtag == false) ? root->right : nullptr;// 递归释放左右子树(仅处理真实子节点)FreeTree(leftChild);FreeTree(rightChild);//释放当前结点delete root;root = nullptr; //指向空,避免野指针
}

若传入结点为空,直接返回。保存当前结点的左右真实结点,仅当标志为false时有效。接着递归释放左右子树,仅处理真实子结点。然后释放当前结点并让当前结点指向空以避免野指针。

三 线索二叉树优化之处

线索二叉树相比传统二叉树的核心优化在于空间利用率遍历效率,主要解决了传统二叉树在遍历和前驱 / 后继查找时的痛点。以下是具体优化点的对比分析:

1. 优化了空指针的浪费,节省存储空间

传统二叉树中,每个节点有两个指针(左 / 右孩子),但实际只有n-1个指针被有效使用(连接子节点),剩余n+1个指针为空(nullptr)。这些空指针未被利用,造成了空间浪费(以n个节点的二叉树为例,空指针占比约 50%)。

线索二叉树通过引入标志位ltag/rtag),将空指针重新定义为 “线索”,存储节点在某种遍历顺序(如中序)中的前驱或后继节点地址。原本被浪费的n+1个空指针被充分利用,实现了空间的高效复用。

2. 优化了遍历的空间复杂度,无需额外栈 / 递归

传统二叉树的遍历(如中序、前序、后序)依赖递归或显式栈结构:

  • 递归遍历:隐含使用调用栈,空间复杂度为O(h)h为树的高度,最坏情况O(n));
  • 迭代遍历:需手动维护栈,空间复杂度同样为O(h)

线索二叉树通过线索直接记录前驱 / 后继关系,遍历过程中无需额外栈或递归,空间复杂度降至O(1)(仅需一个指针变量)。例如,中序线索二叉树的遍历只需从最左节点开始,沿后继线索依次访问即可。

3. 优化了前驱 / 后继的查找效率

在传统二叉树中,查找某个节点的前驱或后继(如中序前驱 / 后继)需要重新遍历整棵树或回溯父节点,时间复杂度为O(n)(最坏情况)。

线索二叉树中,若节点的线索标志位为 1(ltag=1rtag=1),则其前驱 / 后继可直接通过线索指针获取(时间复杂度O(1));若标志位为 0(仍指向子节点),则仅需遍历子树即可找到前驱 / 后继(时间复杂度O(h),优于传统二叉树的O(n))。

总结:优化的本质是 “空间换时间” 的平衡

线索二叉树通过牺牲少量标志位(每个节点增加 2 个int标志),将原本浪费的空指针转化为遍历线索,最终实现:

  • 空间优化:复用空指针,减少额外存储(如栈空间);
  • 时间优化:遍历和前驱 / 后继查找的效率显著提升(尤其在需要频繁遍历的场景中)。

这一设计使得线索二叉树在编译器语法分析、文件系统目录遍历等需要高效顺序访问的场景中被广泛应用。

http://www.xdnf.cn/news/4715.html

相关文章:

  • Arm核的Ubuntu系统上安装Qt
  • 小白借助ai对全栈进行浅浅理解(学习笔记)-Lambda、Optional 避免空指针与新的日期时间 API
  • Linux_进程退出与进程等待
  • 分享 2 款基于 .NET 开源的实时应用监控系统
  • Altera系列FPGA实现图像视频采集转HDMI/LCD输出,提供4套Quartus工程源码和技术支持
  • vue2 结合后端预览pdf 跨域的话就得需要后端来返回 然后前端呈现
  • node.js 实战——在express 中将input file 美化,并完成裁剪、上传进度条
  • 本地可执行命令的智能体部署方案
  • 【WebRTC-12】CreatePeerConnection究竟创建了什么?
  • 开发函数踩坑记 sum(1) over(partition by stock_code order by trade_date asc)
  • 信息系统项目管理工程师备考计算类真题讲解十五
  • java面试OOM汇总
  • 边缘网关(边缘计算)
  • 云平台的技术方向和总体规划
  • 基于卫星遥感数据进行农作物长势监测原理简述
  • BeeWorks IM:专业安全的企业私有化即时通讯软件
  • Linux——Mysql数据库
  • 数据结构*二叉树
  • 软件测试学习笔记
  • 数据结构 - 9( 位图 布隆过滤器 并查集 LRUCache 6000 字详解 )
  • 数据结构 - 10( B- 树 B+ 树 B* 树 4000 字详解 )
  • 谷云科技iPaaS技术实践:集成平台如何解决库存不准等问题
  • 智能外呼机器人的核心优势
  • 《算法导论(第4版)》阅读笔记:p11-p13
  • 图形渲染+事件处理最终版
  • 含铜废水循环利用体系
  • 【杂谈】Godot 2D游戏窗口设置
  • Nginx +Nginx-http-flv-module 推流拉流
  • JAVA:Spring Boot 集成 Lua 的技术博客
  • 深入理解进程与线程、进程池与线程池:企业级开发实战指南