一、基本概念
线索二叉树(Threaded Binary Tree)是在二叉树的结点上加上线索的二叉树。对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域。利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树即称为线索二叉树。这种加上了线索的二叉链表称为线索链表。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
线索二叉树的主要优势在于解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题,同时解决了二叉链表找左、右孩子困难的问题。线索链表使得二叉树的遍历过程线性化,提高了遍历效率,且节约了存储空间。然而,线索二叉树也存在一些不足,如结点的插入和删除操作相对复杂且速度较慢,且线索子树不能共用。
二、构造方法
线索二叉树的构造过程实质上是遍历一棵二叉树的过程。在遍历过程中,访问结点的操作是检查当前的左、右指针域是否为空,将它们改为指向前驱结点或后续结点的线索。
1. 节点结构定义
为了区分线索指针和孩子指针,在每个结点中需要设置两个标志ltag和rtag。这两个标志用于指示leftChild和rightChild指针是指向孩子结点还是线索。具体定义如下:
typedef struct TBTNode {
char data; // 结点数据
int ltag, rtag; // 左右标志
struct TBTNode *lchild; // 左孩子指针
struct TBTNode *rchild; // 右孩子指针
} TBTNode, *BiThrTree;
其中,ltag和rtag的取值为0或1。当ltag为0时,leftChild指向左孩子;当ltag为1时,leftChild指向前驱。同理,当rtag为0时,rightChild指向右孩子;当rtag为1时,rightChild指向后继。
2. 线索化过程
线索化过程以中序遍历为例,其算法思想如下:
初始化一个头结点,并建立头结点与二叉树根结点的指向关系。
遍历二叉树,对于每个结点,检查其左、右指针域是否为空。
如果左指针域为空,且不是头结点的左孩子(即不是遍历序列的第一个结点),则将其左指针域改为指向前驱结点。
如果右指针域为空,则将其右指针域改为指向后继结点。
在遍历过程中,维护一个pre指针,始终指向刚刚访问过的结点,以便设置当前结点的线索。
遍历结束后,需要特殊处理最后一个结点,将其右指针域指向头结点,形成闭环。
3. 示例代码(伪代码)
以下是中序线索化二叉树的伪代码示例:
// 假设pre为全局变量,指向刚刚访问的结点
void InThreading(BiThrTree *p) {
if (p != NULL) {
InThreading(p->lchild); // 遍历左子树
// 设置前驱线索
if (p->lchild == NULL) {
p->ltag = 1;
p->lchild = pre;
}
// 设置后继线索
if (pre != NULL && pre->rchild == NULL) {
pre->rtag = 1;
pre->rchild = p;
}
pre = p; // 更新pre为当前结点
InThreading(p->rchild); // 遍历右子树
}
}
// 初始化头结点并调用线索化函数
BiThrTree InOrderThr(BiThrTree T) {
BiThrTree head = (BiThrTree)malloc(sizeof(TBTNode));
head->ltag = 0;
head->rtag = 1;
head->rchild = head; // 右指针回指,形成闭环
if (T != NULL) {
head->lchild = T; // 头结点指向根结点
pre = NULL; // 初始化pre为NULL
InThreading(T); // 对二叉树进行中序线索化
// 设置最后一个结点的后继线索指向头结点
if (pre != NULL) {
pre->rchild = head;
pre->rtag = 1;
}
// 头结点的左孩子指向根结点,右孩子回指自己
head->lchild->ltag = 0;
}
return head; // 返回头结点
}