本文已参与「新人创作礼」活动,一起开启掘金创作之路。
基本概念
// 线索二叉树的存储结构
typedef struct ThreadNode{
ElemType data; // 数据元素
struct ThreadNode *lchild, *rchild; // 左右孩子指针
int ltag, rtag; // 左右线索标志 =0指向左/右孩子,=1指向前驱/后继
}ThreadNode, *ThreadTree;
-
在传统的二叉树上,若(某结点)无左子树,令 lchild 指向其前驱节点;若无右子树,令 rchild 指向其后继节点
-
标志域的含义
- ltag:为0时表示结点的左孩子,为1时表示结点的前驱
- rtag:为0时表示结点的右孩子,为1时表示结点的后继
-
好处:引入线索二叉树是为了加快查找结点前驱和后继的速度,利用了闲置的 n + 1 个指针
-
普通的二叉树(链表)叫二叉链表;线索二叉树称为线索链表
中序线索二叉树
构造
- 实质:遍历普通的二叉树
// 中序线索化递归算法
void InThread(ThreadTree &p, ThreadTree &pre) {
if(p != NULL) {
InThread(p -> lchild, pre); // 找左子树
if(p -> lchild == NULL) {
p -> lchild = pre; // 左子树为空,建立前驱线索
p -> tag = 1; // 标志位
}
if(pre != NULL && pre -> rchild == NULL) {
pre -> rchild = p; // 右子树为空,建立前驱结点的后继线索
pre -> rtag = 1; // 标志位
}
pre = p; // 标记当前的结点作为刚刚访问的结点
InThread(p -> rchild, pre); // 找右子树
}
}
// 中序线索二叉树构建
void CreateInThread(ThreadTree T) {
ThreadTree pre = NULL;
if(T != NULL) {
InThread(T, pre); // 中序线索化
// 最后一个结点的右子树(可能为空)为最后一个结点的后继线索
pre -> rchild = NULL;
pre -> tag = 1;
}
}
改进:增加一个head指针作为线索链表的头结点,变为双向线索链表
void CreateInThread(ThreadTree T) {
ThreadTree head;
ThreadTree pre = NULL;
if(T != NULL) {
head -> lchild = T; // head的左指针指向线索链表表头
InThread(T, pre); // 中序线索化
head -> rchild = pre; // head的右指针指向线索链表表尾
pre -> rchild = head; // 最后一个结点的右指针指回头结点
pre -> tag = 1; // 标志化
}
}
其他版本的代码(对比参考)(可以使用第二个图自己走一遍)(动图解析)
遍历
// 中序线索二叉树的遍历(不含头结点)
// 第一个结点
ThreadNode *Firstnode(ThreadNode *p) {
while(p -> ltag == 0) p = p -> lchild;
return p;
}
// 结点p的后继
ThreadNode *Nextnode(ThreadNode *p) {
if(p -> rtag == 0) return Firstnode(p -> rchild);
else return p -> rchild;
}
// 最后一个结点
ThreadNode *Lastnode(ThreadNode *p) {
while(p -> rtag == 0) p = p -> rchild;
return p;
}
// 结点p的前驱
ThreadNode *Prenode(ThreadNode *p){
if(p -> ltag == 0) return Fristnode(p -> lchild);
else return p -> lchild;
}
手绘方式
先列出中序遍历序列,然后看是否有左右孩子,没有孩子的那个指针就直接指向前驱/后继
其他的线索方式一样