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

【数据结构】线索二叉树之中序遍历线索化详解与实现

在二叉树的遍历过程中我们会发现大量的空指针域被浪费而线索二叉树的核心思想就是利用这些空指针将其指向节点的前驱或后继节点从而实现二叉树的非递归遍历无需借助栈提升遍历效率。本文将详细讲解中序遍历线索化的原理并通过 C 语言实现完整的中序线索二叉树的构建与遍历。一、线索二叉树的核心概念1. 问题背景普通二叉树中一个具有n个节点的二叉链表共有2n个指针域其中只有n-1个指针域被用来指向孩子节点剩余n1个指针域均为NULL造成了内存空间的浪费。2. 线索化定义利用二叉树的空指针域将空的左指针域指向该节点在中序遍历中的前驱节点将空的右指针域指向该节点在中序遍历中的后继节点这种改造后的二叉树称为线索二叉树这个改造过程称为线索化。3. 标志位设计为了区分指针域是指向孩子节点还是线索需要为每个节点增加两个标志位ltag和rtagltag0左指针域lchild指向该节点的左孩子ltag1左指针域lchild指向该节点的中序前驱节点rtag0右指针域rchild指向该节点的右孩子rtag1右指针域rchild指向该节点的中序后继节点二、线索二叉树的节点结构设计根据上述概念我们设计线索二叉树的节点结构包含数据域、左右指针域、左右标志位C 语言定义如下typedef char ElemType; // 定义线索二叉树的节点结构 typedef struct ThreadNode { ElemType data; // 节点存储的数据 struct ThreadNode *lchild; // 左指针指向左孩子或前驱 struct ThreadNode *rchild; // 右指针指向右孩子或后继 int ltag; // 左标志0 表示左孩子1 表示前驱线索 int rtag; // 右标志0 表示右孩子1 表示后继线索 } ThreadNode; typedef ThreadNode* ThreadTree;三、整体实现思路本次实现将完成普通二叉树构建→中序遍历线索化→线索二叉树的中序非递归遍历全流程核心步骤如下根据前序遍历序列含#表示空节点构建普通二叉树同时初始化标志位定义全局前驱指针prev记录中序遍历中当前节点的前一个节点通过递归实现中序线索化创建头结点优化线索二叉树的遍历让头结点与二叉树首尾节点形成闭环利用线索的特性实现无需栈的中序非递归遍历。四、代码分步实现与解析1. 全局变量与测试序列定义定义前序遍历的测试序列用于构建二叉树、扫描索引和全局前驱指针前驱指针prev用于线索化过程中记录上一个访问的节点。char str[] ABDH##I##EJ###CF##G##; // 前序遍历序列# 表示空节点 int idx 0; // 扫描序列的当前索引 ThreadTree prev; // 全局变量记录中序遍历的前一个节点2. 普通二叉树的构建根据前序遍历序列递归构建二叉树同时为每个节点初始化ltag和rtag若存在左 / 右孩子标志位为 0若为空标志位为 1后续线索化时填充线索。// 创建普通二叉树根据前序字符串str void createTree(ThreadTree *T) { ElemType ch str[idx]; if (ch #) { *T NULL; // 空节点直接置NULL } else { *T (ThreadTree)malloc(sizeof(ThreadNode)); (*T)-data ch; createTree((*T)-lchild); // 递归构建左子树 (*T)-ltag (*T)-lchild ? 0 : 1; // 有左孩子→0无→1 createTree((*T)-rchild); // 递归构建右子树 (*T)-rtag (*T)-rchild ? 0 : 1; // 有右孩子→0无→1 } }3. 中序遍历线索化核心函数线索化的过程基于中序遍历的递归顺序左→根→右在遍历过程中填充空指针的前驱 / 后继线索核心逻辑递归线索化左子树若当前节点左指针为空ltag1将左指针指向前驱节点prev若前驱节点prev的右指针为空prev-rtag1将前驱的右指针指向当前节点即后继更新prev为当前节点递归线索化右子树。// 中序线索化函数建立前驱/后继关系 void threading(ThreadTree T) { if (T ! NULL) { threading(T-lchild); // 递归线索化左子树 // 如果当前节点左指针是空建立前驱线索 if (T-ltag 1) T-lchild prev; // 如果前一个节点的右指针是空建立其后继线索指向当前节点 if (prev prev-rtag 1) prev-rchild T; prev T; // 更新prev为当前节点供后续节点作为前驱 threading(T-rchild); // 递归线索化右子树 } }4. 构建带头部的线索二叉树为了让线索二叉树的遍历更简洁我们创建一个头结点让头结点与二叉树的首尾节点形成闭环规则如下头结点的ltag0lchild指向二叉树的根节点头结点的rtag1rchild指向中序遍历的最后一个节点中序遍历的第一个节点的左指针指向头结点中序遍历的最后一个节点的右指针指向头结点。该函数完成头结点创建、前驱指针初始化、调用线索化函数、补全首尾节点的线索闭环。// 创建头结点调用线索化过程建立完整的线索二叉树 void inOrderThreading(ThreadTree *T, ThreadTree *head) { *head (ThreadTree)malloc(sizeof(ThreadNode)); // 分配头结点空间 (*head)-ltag 0; (*head)-rtag 1; (*head)-rchild *head; // 初始时右指针回指自己 if (*T NULL) { (*head)-lchild *head; // 空树左指针也回指自己 } else { (*head)-lchild *T; // 头结点左指针指向根节点 prev *head; // 初始化前驱指针为头结点 threading(*T); // 对整棵树进行中序线索化 // 补全最后一个节点的后继线索指向头结点 prev-rchild *head; prev-rtag 1; (*head)-rchild prev; // 头结点右指针指向最后一个节点 } }5. 线索二叉树的中序非递归遍历利用线索的特性遍历过程无需借助栈核心思路从头结点的左孩子即根节点开始遍历沿着左孩子一直走到底ltag0时继续找到中序遍历的第一个节点访问当前节点若当前节点有后继线索rtag1则顺着线索访问所有后继节点若当前节点有右孩子rtag0则进入右子树重复上述过程直到遍历回到头结点结束遍历。// 中序遍历线索化后的二叉树非递归无需栈 void inOrder(ThreadTree T) { ThreadTree curr T-lchild; // 从头结点的左子树根节点开始 while (curr ! T) { // 未回到头结点则继续 // 沿着左孩子走到底找到中序第一个节点 while (curr-ltag 0) curr curr-lchild; // 访问当前节点 printf(%c , curr-data); // 顺着后继线索依次访问所有后继节点 while (curr-rtag 1 curr-rchild ! T) { curr curr-rchild; printf(%c , curr-data); } // 进入右子树继续遍历 curr curr-rchild; } }6. 主函数测试主函数按构建普通二叉树→线索化处理→中序遍历的顺序调用函数完成整个流程的测试。int main() { ThreadTree T, head; createTree(T); // 创建原始二叉树 inOrderThreading(T, head); // 执行中序线索化处理 printf(中序遍历结果); inOrder(head); // 遍历线索二叉树 return 0; }7.完整可运行代码#include stdio.h #include stdlib.h // 1. 线索二叉树节点结构定义 // 节点数据类型这里用字符型 typedef char ElemType; // 线索二叉树节点结构体 typedef struct ThreadNode { ElemType data; // 数据域存储节点值 struct ThreadNode *lchild; // 左指针指向左孩子 或 前驱节点 struct ThreadNode *rchild; // 右指针指向右孩子 或 后继节点 int ltag; // 左标志0指向左孩子1指向前驱 int rtag; // 右标志0指向右孩子1指向后继 } ThreadNode; // 重命名指针类型方便使用 typedef ThreadNode *ThreadTree; // 2. 全局变量定义 // 二叉树前序遍历序列#代表空节点用于自动构建二叉树 char str[] ABDH##I##EJ###CF##G##; int idx 0; // 遍历序列的索引指针 ThreadTree prev; // 全局前驱指针记录上一个访问的节点线索化核心 // 3. 创建普通二叉树前序遍历 // 功能根据前序序列生成一棵普通二叉树并初始化标志位 // 参数T 二级指针用于修改根节点指针 void createTree(ThreadTree *T) { // 读取当前字符 char ch str[idx]; // 如果是 #代表空节点 if (ch #) { *T NULL; } else { // 分配节点空间 *T (ThreadTree)malloc(sizeof(ThreadNode)); // 赋值数据域 (*T)-data ch; // 递归创建左子树 createTree((*T)-lchild); // 初始化左标志有左孩子0无左孩子1 (*T)-ltag (*T)-lchild ? 0 : 1; // 递归创建右子树 createTree((*T)-rchild); // 初始化右标志有右孩子0无右孩子1 (*T)-rtag (*T)-rchild ? 0 : 1; } } // 4. 中序遍历线索化核心函数 // 功能递归对二叉树进行中序线索化建立前驱、后继关系 void threading(ThreadTree T) { // 递归终止条件节点为空 if (T NULL) { return; } // 第一步递归线索化 左子树 threading(T-lchild); // 第二步处理当前节点建立线索 // 1. 如果当前节点没有左孩子左指针指向前驱节点 prev if (T-ltag 1) { T-lchild prev; } // 2. 如果上一个节点没有右孩子上一个节点的右指针指向当前节点后继 if (prev ! NULL prev-rtag 1) { prev-rchild T; } // 3. 更新前驱节点为当前节点为下一个节点做准备 prev T; // 第三步递归线索化 右子树 threading(T-rchild); } // 5. 创建带 头结点 的中序线索二叉树 // 功能创建头结点完成整棵树的线索化并形成闭环首尾相连 // 参数T 原树根节点head 输出的线索二叉树头结点 void inOrderThreading(ThreadTree *T, ThreadTree *head) { // 1. 创建头结点 *head (ThreadTree)malloc(sizeof(ThreadNode)); (*head)-ltag 0; // 头结点左标志0 (*head)-rtag 1; // 头结点右标志1 (*head)-rchild *head; // 初始右指针指向自己 // 2. 如果是空树直接让头结点自环 if (*T NULL) { (*head)-lchild *head; } else { // 3. 头结点左指针指向原树的根节点 (*head)-lchild *T; // 4. 初始化前驱节点为头结点 prev *head; // 5. 对整棵树进行中序线索化 threading(*T); // 6. 最后一个节点的右指针指向头结点形成闭环 prev-rchild *head; prev-rtag 1; // 7. 头结点右指针指向最后一个节点 (*head)-rchild prev; } } // 6. 线索二叉树 中序遍历非递归无栈 // 功能利用线索遍历效率极高空间复杂度 O(1) void inOrderTraverse(ThreadTree head) { ThreadTree curr; // curr 指向根节点头结点的左孩子 curr head-lchild; // 循环条件没有回到头结点 while (curr ! head) { // 1. 找到中序遍历的第一个节点一直往左走直到没有左孩子 while (curr-ltag 0) { curr curr-lchild; } // 2. 访问当前节点 printf(%c , curr-data); // 3. 顺着后继线索一直访问 while (curr-rtag 1 curr-rchild ! head) { curr curr-rchild; printf(%c , curr-data); } // 4. 进入右子树继续遍历 curr curr-rchild; } } // 7. 主函数测试 int main() { ThreadTree T; // 原始二叉树根节点 ThreadTree head; // 线索二叉树头结点 // 1. 创建普通二叉树 createTree(T); // 2. 对二叉树进行中序线索化 inOrderThreading(T, head); // 3. 输出遍历结果 printf(线索二叉树 中序遍历结果); inOrderTraverse(head); printf(\n); return 0; }五、运行结果对于测试序列ABDH##I##EJ###CF##G##构建的二叉树中序遍历的结果为六、线索二叉树的优势与注意事项1. 核心优势节省内存利用空指针域存储前驱 / 后继线索无需额外开辟空间遍历高效非递归遍历无需借助栈或队列时间复杂度为O(n)空间复杂度为O(1)仅需少量遍历指针遍历方便可直接通过线索快速找到任意节点的前驱和后继无需重新遍历整棵树。2. 注意事项线索化后不可随意修改二叉树若增删二叉树的节点会破坏已建立的前驱 / 后继线索需要重新进行线索化全局前驱指针的使用本次实现使用全局变量prev简化递归过程也可通过二级指针将prev作为参数传递避免全局变量头结点的作用头结点让线索二叉树形成闭环避免了遍历过程中判断首尾节点的复杂逻辑大幅简化遍历代码。七、总结中序遍历线索化是线索二叉树中最常用的实现方式其核心是基于中序遍历的递归顺序在遍历过程中填充空指针的前驱和后继线索。通过本文的实现我们可以看到线索二叉树通过标志位区分孩子指针和线索指针解决了普通二叉树空指针域浪费的问题带头部的线索二叉树让遍历形成闭环实现了无栈的高效非递归遍历整个线索化过程与中序遍历深度绑定遍历顺序决定了线索的指向关系。线索二叉树不仅适用于中序遍历也可实现前序、后序的线索化其核心思想一致仅需调整线索化的递归顺序即可。掌握中序线索二叉树的实现是理解二叉树遍历优化的重要基础。

相关文章:

【数据结构】线索二叉树之中序遍历线索化详解与实现

在二叉树的遍历过程中,我们会发现大量的空指针域被浪费,而线索二叉树的核心思想就是利用这些空指针,将其指向节点的前驱或后继节点,从而实现二叉树的非递归遍历无需借助栈,提升遍历效率。本文将详细讲解中序遍历线索化…...

2026-04-02 打卡第 2 天

# 2026-04-02 打卡第 2 天 # 列表 """ li [1,2,a] print(li) # 输出结果:[1, 2, a] """# 列表中添加元素 # 整体添加 append """ li [a,b,c] li.append(d) print(li) # 输出结果:[a, b, c, d] "&qu…...

【数据结构与算法】第24篇:哈夫曼树与哈夫曼编码

一、基本概念1.1 带权路径长度在二叉树中:路径长度:从一个节点到另一个节点经过的边数带权路径长度(WPL):所有叶子节点的权重 路径长度 之和示例:text叶子节点:A(7), B(5), C(2), D(4)普通树:15/ \7 8/…...

创意随笔:智能转录便携终端

创意随笔|智能转录便携终端 项目构想 核心亮点 以独立麦克风拾音为核心入口,实现全链路闭环实时翻译 从收音、ASR 识别、翻译、TTS 合成到语音播放/耳机输出,全程不依赖手机或电脑算力,自成一套完整翻译系统,真正做到端…...

技术创业中的风险管理:从内核开发到商业稳定

技术创业中的风险管理:从内核开发到商业稳定 技术创业的风险挑战 作为一名从Linux内核开发者转型产品经理再到科技创业者的人,我深刻体会到风险管理在技术创业中的重要性。技术创业过程中充满了各种风险,从技术风险到商业风险,从市…...

嵌入式开发中的策略模式应用与优化

1. 策略模式在嵌入式开发中的核心价值在嵌入式系统开发中,我们经常遇到这样的场景:同一个功能模块需要根据不同的硬件环境、运行状态或外部条件采用不同的处理算法。传统做法是使用大量的if-else或switch-case语句,但这种做法会带来几个显著问…...

技术创业中的产品迭代:从内核开发到用户中心

技术创业中的产品迭代:从内核开发到用户中心 产品迭代的重要性 作为一名从Linux内核开发者转型产品经理再到科技创业者的人,我深刻体会到产品迭代在技术创业中的重要性。一个成功的产品不是一蹴而就的,而是通过不断的迭代和优化逐步发展起来的…...

【图像加密】基于 AES算法的图像位平面加密解密算法附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…...

OpenClaw性能调优实战:Qwen3-32B在RTX4090D上的量化推理加速

OpenClaw性能调优实战:Qwen3-32B在RTX4090D上的量化推理加速 1. 为什么需要性能调优? 去年冬天,当我第一次在RTX4090D上部署Qwen3-32B模型时,本以为24GB显存足以轻松应对各种任务。但现实很快给我上了一课——一个简单的网页内容…...

IBM与Arm合作推进双架构主机系统开发

IBM和Arm宣布合作开发能够运行IBM和Arm双重工作负载的硬件,使Arm软件能够在IBM主机上运行。两家公司计划在三个方面展开合作:构建虚拟化工具,让Arm软件能够在IBM平台上运行;确保Arm应用程序符合受监管行业必须遵循的安全和数据驻留…...

AWS推出新工具简化量子纠错开发流程

谷歌近日将量子计算机实用化时间表提前至2029年,这得益于量子计算机硬件、量子纠错和算法方面的重大改进。2019年,谷歌估计需要2000万个量子比特才能破解RSA加密。到2025年5月,谷歌将这一估计数字下调至100万个。今年2月,澳大利亚…...

DuinoMemory:面向Arduino的轻量级嵌入式智能指针库

1. 项目概述DuinoMemory 是一款专为 Arduino 及资源受限嵌入式系统设计的轻量级智能指针库。它不依赖 STL、不使用异常(exceptions)、不启用 RTTI,完全以头文件形式提供(header-only),所有实现均通过 C 模板…...

作家使用AI写小说:写作者必须接纳人工智能但我们依然珍贵

我最近在游乐场听到一段对话,这比任何分析师对泡沫的预测都更应该让AI公司高管担忧。一个男孩和一个女孩,大概10岁,正在争吵。"那是AI!那是AI!"女孩喊道。她的意思是男孩在沉溺于一种新的特殊胡言乱语&#…...

OpenAI收购科技脱口秀TBPN,力图塑造AI叙事话语权

OpenAI正通过收购备受硅谷内部人士关注的科技脱口秀TBPN进军媒体行业,该节目主持人周三宣布了这一消息。联合主持人约翰库根和乔迪海斯每个工作日从洛杉矶直播TBPN节目三小时,邀请的嘉宾包括创业者、风险投资家和科技界重要人物。此次交易的财务条款未予…...

OpenClaw压力测试:千问3.5-27B持续运行48小时稳定性报告

OpenClaw压力测试:千问3.5-27B持续运行48小时稳定性报告 1. 测试背景与设计思路 上周在星图平台部署了千问3.5-27B镜像后,我决定对OpenClaw框架进行极限压力测试。这个想法源于实际需求——作为独立开发者,经常需要AI助手连续处理夜间数据抓…...

嵌入式开发中PC与嵌入式思维的融合实践

1. 嵌入式开发中的PC思维与嵌入式思维融合作为一名从PC端开发转向嵌入式领域的工程师,我深刻体会到两种思维方式的差异与互补。PC编程注重抽象层次和开发效率,而嵌入式编程则必须关注硬件特性和实时性。真正的高手往往能将二者有机结合。在嵌入式领域&am…...

嵌入式软件架构设计:基础设施层实践指南

1. 嵌入式软件架构设计概述作为一名在嵌入式领域摸爬滚打多年的工程师,我深知软件架构设计的重要性。很多人认为架构设计是资深工程师的专利,其实不然。就像盖房子需要先打地基一样,任何规模的嵌入式项目都需要合理的架构设计作为基础。嵌入式…...

电动关节机械手设计【任务书+说明书+CAD图纸】 电动关节机器人

电动关节机械手作为工业自动化领域的核心装备,通过电机驱动实现多自由度运动控制,在物料搬运、装配加工等场景中承担关键操作任务。其核心作用在于替代人工完成重复性高、精度要求严苛的作业,例如精密电子元件的抓取、重型工件的定位等&#…...

4大技术方案解决WarcraftHelper工具的《魔兽争霸III》兼容性与性能优化问题

4大技术方案解决WarcraftHelper工具的《魔兽争霸III》兼容性与性能优化问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper WarcraftHelper是一款专注…...

折腾光纤模型的手记

comsol仿真-W型光子晶体光纤色散与损耗分析效果展示最近在实验室被导师催着搞光子晶体光纤的仿真,W型结构这种带双包层设计的玩意儿确实有点意思。作为COMSOL萌新,边啃说明书边试错,折腾一周终于把色散曲线和损耗谱给整明白了。先说建模这个重…...

针对双SMC控制的四轮转向轨迹跟踪模型优化与效果评估研究

四轮转向4WS轨迹跟踪控制模型 采用双SMC控制 4WS通过积分滑模控制跟踪期望横摆角速度和质心侧偏角,效果很好~ 轨迹跟踪为双移线输入,采用积分滑模控制 【特别说明】 是针对两篇论文的控制进行复现哦~ 提供参考文献及模型文件 最近在复现四轮转向轨迹跟踪…...

直接可用4轴插补算法库,STM32的DDA插补联动与梯形加减速算法代码

可以直接使用的4轴插补算法库,不是丢给你一堆gr1b或者写字机或者3d打印的开源代码,本运控库上项目级别的,需要添加在自己的项目中,不支持gm码,只有运动控制核心代码,可以添加在自己项目中的,stm…...

光储并网直流微电网仿真模型(matlab/simulink,2018),包含: 1.MPPT模块

光储并网直流微电网仿真模型(matlab/simulink,2018),包含: 1.MPPT模块,实现光伏输入最大功率跟踪; 2.储能电池模块; 3.超级电容模块; 控制策略简介: 糸统使用…...

质子交换膜(PEM)燃料电池氢气供应系统,阳极压力非线性状态控制simulink模型;自适应反...

质子交换膜(PEM)燃料电池氢气供应系统,阳极压力非线性状态控制simulink模型;自适应反步法控制; 燃料电池电堆模型:阴极流道,阳极流道,膜水合传递,输出电压模型、 氢气回路…...

MAX9814麦克风音量LED指示器嵌入式固件库

1. 项目概述MAX9814_Electret_Microphone_LED_Volume_Indicator是一个面向嵌入式音频前端采集与可视化反馈的轻量级固件库,专为 Adafruit MAX9814 电容式驻极体麦克风放大模块设计。该模块基于 Maxim(现为 Analog Devices)推出的低噪声、高增…...

L293D电机驱动库:嵌入式直流电机控制实战指南

1. L293D电机驱动库深度解析:面向嵌入式工程师的工程实践指南L293D是TI(德州仪器)推出的双H桥直流电机驱动芯片,广泛应用于Arduino、ESP32等微控制器平台的中小功率直流电机控制场景。本库并非简单封装GPIO操作,而是针…...

C语言整数字节拆解:联合体与移位操作详解

1. 理解题目:整数字节拆解的核心需求 在嵌入式开发和底层系统编程中,处理多字节数据是家常便饭。就拿这个面试题来说,我们需要从32位无符号整数0x12345678中提取出它的四个独立字节。这看似简单的需求背后,其实涉及到计算机系统中…...

剪映自动化工具来了:AI帮你自动剪辑成片

文章目录 📖 介绍 📖 🏡 演示环境 🏡 📒 AI赋能剪映自动化剪辑 📒 🎯 设计理念 🔧 核心功能 📦 安装与使用 ⚓️ 相关链接 ⚓️ 📖 介绍 📖 在视频创作中,剪辑工作往往耗时耗力。从素材导入、字幕匹配、BGM选择到最终导出,每一个环节都需要创作者投入大…...

从裸机开发到RTOS:嵌入式系统进阶指南

1. 裸机开发的本质与局限性裸机开发,顾名思义就是在没有任何操作系统支持下直接对硬件进行编程。这种方式在嵌入式系统入门阶段非常普遍,尤其适合资源极其有限的8位单片机(如51系列)或简单应用场景。但当我们面对STM32这类性能强大…...

MS5540C传感器驱动开发:类SPI协议与校准算法详解

1. MS5540C传感器库深度解析:面向嵌入式工程师的底层驱动开发指南 MS5540C系列是TE Connectivity(原Measurement Specialties)推出的高精度、低功耗数字压力/温度复合传感器,广泛应用于潜水设备、气象站、工业过程监控及水下机器人…...