总目录:https://blog.csdn.net/treesorshining/article/details/125726400
文章目录
1.概述
在含有n个结点的二叉树中,有n+1个空指针,即每个度为1的结点有一个空指针,每个叶子结点有两个空指针,这就造成了空间的浪费。可以通过一些方法将这些空指针充分利用起来,即将这些空指针改成指向前驱或后继的线索,可以视为在原本的二叉树上用空指针建立一个线索链表,加上了线索的二叉树也可称为线索二叉树。
2.基本操作
2.1 结构体构造
// 线索二叉树结点
typedef struct ThreadNode {
char data; // 数据域
// 左孩子、右孩子
struct ThreadNode *lchild, *rchild;
// 左、右线索标志
// tag==0表示指针指向孩子
// tag==1表示指针指向线索
int ltag, rtag;
} ThreadNode, *ThreadTree;
// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
2.2 初始化
建立一个根节点,同时将左右孩子指针置空,将左右tag设为0。
// 初始化
void InitTree(ThreadNode* &T) {
// 初始化可将根节点置空,空树
T = NULL;
// 插入根节点
// 分配空间
T = (ThreadNode*)malloc(sizeof(ThreadNode));
// 赋值
T->data = 'A';
// 初始tag设为0
T->ltag = 0;
T->rtag = 0;
// 初始无左右子树
T->lchild = NULL;
T->rchild = NULL;
}
2.3 左插入结点
与一般二叉树插入类似,不过要注意修改tag的值。
// 左插入结点
bool InsertLeftTreeNode(ThreadNode* &T, char x) {
// 分配空间
ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 将tag值设为0
p->ltag = 0;
p->rtag = 0;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->lchild = p;
return true;
}
2.4 右插入结点
与一般二叉树插入类似,不过要注意修改tag的值。
// 右插入结点
bool InsertRightTreeNode(ThreadNode* &T, char x) {
// 分配空间
ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 将tag值设为0
p->ltag = 0;
p->rtag = 0;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->rchild = p;
return true;
}
2.5 访问结点并建立线索
遍历中访问结点,在遍历的同时建立前驱及后继线索。
// 用于访问结点
void visit(ThreadNode *q) {
// 左子树为空,建立前驱线索
if(q->lchild == NULL) {
// 令其左指针指向前驱
q->lchild = pre;
// 将tag值修改为1,表示此处左指针指向的是前驱,即左孩子指针是线索
q->ltag = 1;
if(pre != NULL) {
printf("建立前驱线索:%c-->%c\n", q->data, pre->data);
} else {
printf("建立前驱线索:%c-->%s\n", q->data, "NULL");
}
}
// 设立后继线索时前驱不能为空(无前驱无法建立后继)
// 右孩子指针为空,表示可以建立后继线索
if(pre != NULL && pre->rchild == NULL) {
// 建立前驱结点的后继线索
pre->rchild = q;
// 将tag值修改为1,表示此处右指针指向的是后继,即右孩子指针是线索
pre->rtag = 1;
printf("建立后继线索:%c-->%c\n", pre->data, q->data);
}
// 线索建立完成后,前驱指针移动,指向当前指向结点处
pre = q;
}
2.6 中序遍历二叉树并建立线索
实际上就是进行中序遍历,一边遍历一边线索化,因为想得到一个结点的前驱或者后继,只能在遍历时取得。
// 中序线索化二叉树
// 实际上就是进行中序遍历,一边遍历一边线索化
// 因为想得到一个结点的前驱或者后继,只能在遍历时取得
// 线索化在visit函数中完成
void InThread(ThreadTree T) {
if(T != NULL) {
InThread(T->lchild);
visit(T);
InThread(T->rchild);
}
}
2.7 中序线索化二叉树
// 中序线索化二叉树
void CreateInThread(ThreadTree T) {
// pre初始为NULL
pre = NULL;
// 若二叉树为空,则不可线索化
if(T != NULL) {
// 中序线索化二叉树
InThread(T);
// 对于最后一个结点的处理
// 由于访问完成后,pre和p都指向最后一个结点
// 按照线索化的要求,如果最后一个结点右孩子指向NULL,则可视为其有后继结点
// 应将tag设为1
if(pre->rchild == NULL) {
pre->rtag = 1;
printf("建立后继线索:%c-->%s\n", pre->data, "NULL");
}
}
}
2.8 找到第一个被中序遍历的结点
找到以p为根的子树中,第一个被中序遍历的结点。
// 找到以p为根的子树中,第一个被中序遍历的结点
// 即后继结点
ThreadNode *FirstNode(ThreadNode *p) {
// 循环找到最左下结点(不一定是叶子结点)
// 当ltag==0时,说明其线索二叉树此结点不存在左线索,那么一定有左子树
while(p->ltag == 0) {
p = p->lchild;
}
return p;
}
2.9 在中序线索二叉树中找到某一结点的后继结点
要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可,如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右。如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点,如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点。
// 在中序线索二叉树中找到结点p的后继结点
// 要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右
// 如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点
// 如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点
// 注意:若没有线索,则一定存在右子树(根据线索二叉树特点)
ThreadNode *NextNode(ThreadNode *p) {
// 如果右孩子结点无线索
if(p->rtag == 0) return FirstNode(p->rchild);
// 如果右孩子结点有线索则直接利用线索找到结点即可
else return p->rchild;
}
2.10 利用中序线索树完成非递归中序遍历
// 对中序线索二叉树进行中序遍历(利用线索非递归实现)
// 首先找到根节点最左下结点,访问之后,通过NextNode函数找后继结点访问即可
void InOrder(ThreadNode *T) {
for(ThreadNode *p = FirstNode(T);p != NULL;p = NextNode(p)) {
visit1(p);
}
}
3.完整代码
#include<stdio.h>
#include<stdlib.h>
// 线索二叉树结点
typedef struct ThreadNode {
char data; // 数据域
// 左孩子、右孩子
struct ThreadNode *lchild, *rchild;
// 左、右线索标志
// tag==0表示指针指向孩子
// tag==1表示指针指向线索
int ltag, rtag;
} ThreadNode, *ThreadTree;
// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
// 初始化
void InitTree(ThreadNode* &T) {
// 初始化可将根节点置空,空树
T = NULL;
// 插入根节点
// 分配空间
T = (ThreadNode*)malloc(sizeof(ThreadNode));
// 赋值
T->data = 'A';
// 初始tag设为0
T->ltag = 0;
T->rtag = 0;
// 初始无左右子树
T->lchild = NULL;
T->rchild = NULL;
}
// 左插入结点
bool InsertLeftTreeNode(ThreadNode* &T, char x) {
// 分配空间
ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 将tag值设为0
p->ltag = 0;
p->rtag = 0;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->lchild = p;
return true;
}
// 右插入结点
bool InsertRightTreeNode(ThreadNode* &T, char x) {
// 分配空间
ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 将tag值设为0
p->ltag = 0;
p->rtag = 0;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->rchild = p;
return true;
}
// 用于访问结点
void visit(ThreadNode *q) {
// 左子树为空,建立前驱线索
if(q->lchild == NULL) {
// 令其左指针指向前驱
q->lchild = pre;
// 将tag值修改为1,表示此处左指针指向的是前驱,即左孩子指针是线索
q->ltag = 1;
if(pre != NULL) {
printf("建立前驱线索:%c-->%c\n", q->data, pre->data);
} else {
printf("建立前驱线索:%c-->%s\n", q->data, "NULL");
}
}
// 设立后继线索时前驱不能为空(无前驱无法建立后继)
// 右孩子指针为空,表示可以建立后继线索
if(pre != NULL && pre->rchild == NULL) {
// 建立前驱结点的后继线索
pre->rchild = q;
// 将tag值修改为1,表示此处右指针指向的是后继,即右孩子指针是线索
pre->rtag = 1;
printf("建立后继线索:%c-->%c\n", pre->data, q->data);
}
// 线索建立完成后,前驱指针移动,指向当前指向结点处
pre = q;
}
// 中序线索化二叉树
// 实际上就是进行中序遍历,一边遍历一边线索化
// 因为想得到一个结点的前驱或者后继,只能在遍历时取得
// 线索化在visit函数中完成
void InThread(ThreadTree T) {
if(T != NULL) {
InThread(T->lchild);
visit(T);
InThread(T->rchild);
}
}
// 中序线索化二叉树
void CreateInThread(ThreadTree T) {
// pre初始为NULL
pre = NULL;
// 若二叉树为空,则不可线索化
if(T != NULL) {
// 中序线索化二叉树
InThread(T);
// 对于最后一个结点的处理
// 由于访问完成后,pre和p都指向最后一个结点
// 按照线索化的要求,如果最后一个结点右孩子指向NULL,则可视为其有后继结点
// 应将tag设为1
if(pre->rchild == NULL) {
pre->rtag = 1;
printf("建立后继线索:%c-->%s\n", pre->data, "NULL");
}
}
}
// 找到以p为根的子树中,第一个被中序遍历的结点
// 即后继结点
ThreadNode *FirstNode(ThreadNode *p) {
// 循环找到最左下结点(不一定是叶子结点)
// 当ltag==0时,说明其线索二叉树此结点不存在左线索,那么一定有左子树
while(p->ltag == 0) {
p = p->lchild;
}
return p;
}
// 在中序线索二叉树中找到结点p的后继结点
// 要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右
// 如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点
// 如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点
// 注意:若没有线索,则一定存在右子树(根据线索二叉树特点)
ThreadNode *NextNode(ThreadNode *p) {
// 如果右孩子结点无线索
if(p->rtag == 0) return FirstNode(p->rchild);
// 如果右孩子结点有线索则直接利用线索找到结点即可
else return p->rchild;
}
// 访问结点
void visit1(ThreadNode *T) {
printf("%c ", T->data);
}
// 对中序线索二叉树进行中序遍历(利用线索非递归实现)
// 首先找到根节点最左下结点,访问之后,通过NextNode函数找后继结点访问即可
void InOrder(ThreadNode *T) {
for(ThreadNode *p = FirstNode(T);p != NULL;p = NextNode(p)) {
visit1(p);
}
}
// 找到以p为根的子树中最后一个被中序遍历的结点
// 即前驱结点
ThreadNode *LastNode(ThreadNode *p) {
// 循环找到最右下结点(不一定为叶子结点)
// 当rtag==0时,说明其线索二叉树此结点不存在右线索,那么一定有右子树
while(p->rtag == 0) {
p = p->rchild;
}
return p;
}
// 在中序线索二叉树中找到结点p的前驱结点
// 要找到一个结点的前驱,如果左孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找前驱结点,即左根右
// 即应该找到其左子树中遍历的最后一个结点,即左子树最右下结点,那即是其前驱结点
// 注意:若没有线索,则一定存在左子树(根据线索二叉树特点)
ThreadNode *PreNode(ThreadNode *p) {
// 左子树中最右下结点
if(p->ltag == 0) {
return LastNode(p->lchild);
} else {
// 如果右孩子结点有线索则直接利用线索找到结点即可
return p->lchild;
}
}
int main() {
ThreadTree T;
InitTree(T);
InsertLeftTreeNode(T, 'B');
InsertRightTreeNode(T, 'C');
InsertLeftTreeNode(T->lchild, 'D');
InsertRightTreeNode(T->lchild, 'E');
InsertLeftTreeNode(T->rchild, 'F');
InsertRightTreeNode(T->rchild, 'G');
CreateInThread(T);
printf("InOrder\n");
InOrder(T);
}