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

线索二叉树

线索二叉树(Threaded Binary Tree) 是对普通二叉树的一种优化。在普通的二叉树链式存储中,会有大量的空指针,线索二叉树正是利用这些空指针来存放指向节点在某种遍历序列下的“前驱”和“后继”的记录。


1. 为什么需要线索二叉树?

在含有 $n$ 个结点的二叉链表中,共有 $2n$ 个指针域(每个结点有左右两个)。而有效指针(连接子结点的)只有 $n-1$ 条。因此,会有 $n+1$ 个空指针域 被浪费了。

线索化(Threading) 的目的就是:

  1. 利用空指针: 让原本指向 NULL 的左指针指向其前驱(Predecessor),右指针指向其后继(Successor)
  2. 提高遍历效率: 可以在不使用递归和栈的情况下,像遍历链表一样线性地遍历二叉树。

2. 线索二叉树的结构

为了区分一个节点的 leftright 指针指向的是“孩子”还是“线索”,我们需要在节点结构中增加两个标志位(Tags):

  • ltag:
    • 0: left 指向左孩子。
    • 1: left 指向前驱。
  • rtag:
    • 0: right 指向右孩子。
    • 1: right 指向后继。

C++ 节点定义

C++

struct ThreadNode {int data;ThreadNode *left, *right;int ltag, rtag; // 0: 孩子, 1: 线索ThreadNode(int val) : data(val), left(nullptr), right(nullptr), ltag(0), rtag(0) {}
};

3. 中序线索二叉树 (In-order Threaded Tree)

最常见的是中序线索二叉树。它的线索化过程是按照中序遍历(左-根-右)的顺序进行的。

[Image comparing a normal binary tree and its in-order threaded version with dotted lines for threads]

核心算法:中序线索化

线索化的本质是在遍历的过程中,用一个全局变量 pre 记录当前节点的前一个节点。

C++

ThreadNode* pre = nullptr; // 全局变量,记录刚刚访问过的节点void createInThread(ThreadNode* curr) {if (curr == nullptr) return;// 1. 递归线索化左子树createInThread(curr->left);// 2. 处理当前节点// 如果左孩子为空,建立前驱线索if (curr->left == nullptr) {curr->left = pre;curr->ltag = 1;}// 如果 pre 的右孩子为空,建立后继线索if (pre != nullptr && pre->right == nullptr) {pre->right = curr;pre->rtag = 1;}pre = curr; // 更新 pre// 3. 递归线索化右子树createInThread(curr->right);
}

4. 遍历线索二叉树

线索化完成后,遍历二叉树就不再需要递归或栈了,效率接近线性链表。

寻找中序后继的逻辑:

  • 如果 rtag == 1,则 right 直接就是后继。
  • 如果 rtag == 0,说明有右子树,后继则是右子树中最左下角的节点

C++

// 寻找以 p 为根的子树中,中序遍历的第一个节点
ThreadNode* firstNode(ThreadNode* p) {while (p->ltag == 0) p = p->left;return p;
}// 寻找节点 p 的中序后继
ThreadNode* nextNode(ThreadNode* p) {if (p->rtag == 0) return firstNode(p->right);else return p->right; // rtag == 1,直接返回后继线索
}// 中序遍历整个线索树
void inorderTraversal(ThreadNode* root) {for (ThreadNode* p = firstNode(root); p != nullptr; p = nextNode(p)) {cout << p->data << " ";}
}

5. 线索二叉树的优缺点

优点:

  1. 空间利用率高: 充分利用了 $n+1$ 个空闲指针。
  2. 查找快: 寻找前驱和后继节点的操作非常快。
  3. 非递归: 遍历时不需要栈,节省了系统开销,也不会有栈溢出的风险。

缺点:

  1. 复杂度增加: 插入和删除节点的操作变得非常复杂,因为需要维护线索的正确性。
  2. 额外空间: 每个节点多了两个 tag 标志位(虽然占用的内存远小于指针)。
http://www.jsqmd.com/news/115855/

相关文章:

  • 实用指南:【javaEE】多线程进阶--CAS与原子类
  • 第3章 字符串向量数组
  • 大模型微调实战指南:从全参数微调到BitFit的低成本学习路径
  • 灵活用工平台,我的实践复盘
  • 敢不敢用一年时间读完这12本书,模型入门必看的12本书!建议收藏!!
  • 曲线的极坐标方程输入法 | Desmos 玩法系列 02
  • Windows10/11右键-超级菜单(动态菜单)批处理安装,所有任务、环境变量、设备管理器、网络链接、设备和打印机、重启资源管理器、电源选项、 区域语言、查看串口、获取本机IP等
  • 卡帕西年度预测:大模型只释放10%潜力,2025年AI发展6大趋势
  • AVL
  • STM32学习——编码器接口测速
  • AI写论文哪个软件好?9款AI论文写作软件实测,查重率低至6%!
  • 鸿蒙系统
  • 11.20 脚本网页 数学分支
  • 学Simulink——基础MPPT控制场景实例:基于Simulink的电导增量法(INC)光伏MPPT仿真
  • 本地运行可以打印东西,docker run后却没有日志产生?记录一次AI编程的小蠢行为
  • 排序--基数排序
  • 正点原子移植linux-imx6.12的一个容易犯得问题
  • 大模型微调优化方案:PEFT-LoRA轻量化与量化技术,成本降低70%训练周期缩短65%
  • 解析:One-API 与 New-API 核心原理
  • 模板和策略模式的区别
  • 好友圈模块 Cordova 与 OpenHarmony 混合开发实战
  • 学Simulink——机器人控制场景实例:基于Simulink的SCARA机械臂关节空间PD控制仿真
  • 第4章 运算符
  • 工厂模式和抽象工厂模式的区别
  • 洞察:MCP与Function Calling区别
  • 一文搞懂DNAT与SNAT:内网外网通信的“流量翻译官”
  • 3D打印与低压灌注硅胶复模小批量零件生产制造
  • 快!太快了!一键生成!一键导出!微信自动统计数据报表来了!
  • 抽象工厂
  • 对比:字节DeerFlow与阿里DeepResearch