一,基本概念
线索二叉树,即在二叉链表的基础上,将二叉链表的空指针域指向其前驱节点和后继节点
线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索
线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化
线索链表:加上线索的二叉链表称为线索链表(或线索二叉树)
二,线索二叉树的构建(中序)
(1)结点结构
可以发现,相较于原来的二叉链表,这里在数据结构里添加了两个int类型,来规定左右指针的作用
由下图可见,有的左右指针是连接的子树,没有链接左右子树的指针现在就通过红色的虚线指向了该结点的前驱或者后继(中序)(图片参考懒猫老师《数据结构》相关课程笔记)
下图中序遍历:DGBAECF
特别的是,对于D和F,D是中序遍历的开头结点,它没有前驱节点;F是中序遍历的结尾的结点,它没有后继节点,那么这时,D的左指针为NULL,ltag=1;F的右指针为NULL,rtag=1
(2)链表创建过程
首先先初始化建立一个二叉树,具体相关步骤在这篇文章中可以找到~
二叉树的遍历和创建https://blog.csdn.net/m0_63223213/article/details/125803613?spm=1001.2014.3001.5501再通过以下步骤创建一个线索二叉树:(下图是中序线索二叉树的结构示意)
具体过程如下:
p:正在访问的结点
pre:刚访问的结点
按照中序遍历的顺序递归进行上面的步骤,就可以将所有NULL的指针域都赋上前驱或者后继,从而实现线索二叉树的构建
(这里特别记录下,因为要递归调用,普通声明的话,pre指针的值每次调用都会被初始化,从而产生错误;解决方案有两种,一种是使用static类型声明,后续pre的值就是正确变化的,另一种是在函数声明中再添加一个参数BiNode **pre,递归调用时也传地址,就不会出错)
代码实现:
void ThreadBiTree_mid(BiNode *p) { //树的中序遍历,即中序线索二叉树的构建
static BiNode *pre = NULL; //递归的过程中不会被重复的初始化
if (p == NULL) //递归调用的出口
return;
ThreadBiTree_mid(p->Lchild);//左子树线索化
if (p->Lchild == NULL) { //建立p的前驱线索
p->Ltag = 1;
p->Lchild = pre;
}
if (pre != NULL && pre->rchild == NULL) { //建立pre的后继线索
pre->rtag = 1;
pre->rchild = p;
}
pre = p;
ThreadBiTree_mid(p->rchild);//右子树线索化
}
(3)寻找结点的前驱/后继结点
寻找的规则是,如果结点的Ltag==1代表可以直接找到前驱节点;rtag==1代表可以直接找到后继结点,否则,说明二叉树该结点有左子树或者右子树;遵循以下规则来找前驱/后继:
代码实现:
BiNode *Findnext(BiNode *root) { //中序线索链表中查找传入结点的后继
BiNode *q = NULL;
if (root == NULL)
return NULL;
if (root->rtag == 1) { //右标志为1,可直接得到后继结点
q = root->rchild;
} else { //右标志为0,说明有右子树,则寻找右子树最左下角的结点
q = root->rchild;
while (q->Ltag == 0) //查找最左下角的位置
q = q->Lchild;
}
return q;//返回后继结点
}
BiNode *Findprior(BiNode *root) { //中序线索链表中查找传入结点的前驱
BiNode *q;
if (root == NULL)
return NULL;
if (root->Ltag == 1) { //左标志为1.可直接得到前驱结点
q = root->Lchild;
} else { //左标志为0,说明有左子树,则寻找左子树最右下角的结点
q = root->Lchild;
while (q->rtag == 0)
q = q->rchild;
}
return q;
}
三,完整代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef char Datatype;
//测试用例:ABD#F##G##C#E##
typedef struct BiNode {
Datatype data;//数据内容
struct BiNode *Lchild;//指向左孩子结点
struct BiNode *rchild;//指向右孩子结点
int Ltag;//值为0,则Lchild指向该结点的左孩子;值为1.指向该结点的前驱结点
int rtag;//值为0,则rchild指向该结点的右孩子;值为1.指向该结点的后继结点
} BiNode ;
BiNode *Creat(char *str, int *i, int len) { //树的创建
struct BiNode *bt = NULL;
char ch = str[(*i)++];
if (ch == '#' || *i >= len) {
bt = NULL;
} else {
bt = (struct BiNode *)malloc(sizeof(BiNode));
if (bt != NULL) {
bt->data = ch;
bt->Ltag = 0;
bt->rtag = 0;
bt->Lchild = Creat(str, i, len); //这里的递归要赋值,这样才能建立不同域中的连接关系
bt->rchild = Creat(str, i, len);
}
}
return bt;//返回的一直是根结点
}
void MidOrder(BiNode *bt) { //非线索二叉树的中序遍历
if (bt == NULL) //递归调用的出口
return;
else {
MidOrder(bt->Lchild);//前序递归遍历bt的左子树
visit(bt->data);//访问根节点bt的数据域
MidOrder(bt->rchild);//前序递归遍历bt的右子树
}
}
void ThreadBiTree_mid(BiNode *p) { //树的中序遍历,即中序线索二叉树的构建
static BiNode *pre = NULL; //递归的过程中不会被重复的初始化
if (p == NULL) //递归调用的出口
return;
ThreadBiTree_mid(p->Lchild);//左子树线索化
if (p->Lchild == NULL) { //建立p的前驱线索
p->Ltag = 1;
p->Lchild = pre;
}
if (pre != NULL && pre->rchild == NULL) { //建立pre的后继线索
pre->rtag = 1;
pre->rchild = p;
}
pre = p;
ThreadBiTree_mid(p->rchild);//右子树线索化
}
BiNode *Findnext(BiNode *root) { //中序线索链表中查找传入结点的后继
BiNode *q = NULL;
if (root == NULL)
return NULL;
if (root->rtag == 1) { //右标志为1,可直接得到后继结点
q = root->rchild;
} else { //右标志为0,说明有右子树,则寻找右子树最左下角的结点
q = root->rchild;
while (q->Ltag == 0) //查找最左下角的位置
q = q->Lchild;
}
return q;//返回后继结点
}
BiNode *Findprior(BiNode *root) { //中序线索链表中查找传入结点的前驱
BiNode *q;
if (root == NULL)
return NULL;
if (root->Ltag == 1) { //左标志为1.可直接得到前驱结点
q = root->Lchild;
} else { //左标志为0,说明有左子树,则寻找左子树最右下角的结点
q = root->Lchild;
while (q->rtag == 0)
q = q->rchild;
}
return q;
}
void visit(Datatype e) {
printf("%c ", e);
}
void inOrder(BiNode *root) { //中序线索链表的(顺着的)遍历算法
BiNode *p;
if (root == NULL) //根结点
return;
p = root;
// while (p->Ltag == 0) //查找中序遍历的第一个结点p
while (p->Lchild != NULL)
p = p->Lchild;
visit(p->data);//访问第一个结点
while (p->rchild != NULL) { //当p结点存在后继
p = Findnext(p);
visit(p->data);
}
}
void reverseinOrder(BiNode *root) { //中序线索链表的(逆着的)遍历算法
BiNode *p;
if (root == NULL) //根结点
return;
p = root;
while (p->rchild != NULL) //查找中序遍历的最后一个结点p
p = p->rchild;
visit(p->data);//访问最后一个结点
while (p->Lchild != NULL) { //当p结点存在前驱
p = Findprior(p);
visit(p->data);
}
}
void freeThread(BiNode *p) {
BiNode *pre;
if (p == NULL) //根结点
return;
while (p->Lchild != NULL)
p = p->Lchild;//寻找中序遍历第一个结点
while (p->rchild != NULL) { //当p结点存在后继
pre = Findnext(p);
free(p);
p = pre;
}
printf("二叉树释放成功!\n");
}
main() {
BiNode *bt;
int i = 0, len;
char str[50];
printf("输入一个字符串用于建立二叉树:");
scanf("%s", str);
len = strlen(str);
bt = Creat(str, &i, len);
printf("测试输出初始中序二叉树遍历结果:");
MidOrder(bt);
printf("\n");
printf("构建线索二叉树:\n");
ThreadBiTree_mid(bt);//构建中序线索二叉树
printf("测试顺序输出中序线索二叉树遍历结果:");
inOrder(bt);
printf("\n");
printf("测试逆序输出中序线索二叉树遍历结果:");
reverseinOrder(bt);
printf("\n");
freeThread(bt);
}
测试输出:
初学小白,有错误欢迎指正喔!~