线索二叉树
传统的二叉链表存储二叉树,仅能表示一种父子关系,不能直接得到结点在遍历过程中的前驱和后继,而且,有n个结点的二叉树使用二叉链表存储时,存在n+1个空指针。因此有人猜想能否利用这些空指针来存放其遍历前驱或遍历后继的指针,用来加快查找结点前驱和后继的速度?于是,线索二叉树诞生了。
线索二叉树规定:
- 若结点无左孩子,则令lchild指向其遍历前驱。
- 若结点无右孩子,则令rchlid指向其遍历后继。
当然,为了表明判断结点无左左孩子、无右孩子,因此线索二叉树还需要增加了两个标识,ltag和rtag来表明结点是否存在左孩子、右孩子。
线索二叉树的结点存储结构
// C++
class ThreadNode{
public:
// 这里省略数据域
ThreadNode *lchild, *rchild;
bool ltag,rtag;
};
以这种结点构成的二叉链表作为二叉树的存储结构,我们称为线索链表。
其中指向结点遍历前驱和遍历后继的指针我们称为线索。
加上线索的二叉树,我们称为线索二叉树。
二叉树线索化
二叉树的线索化是指将二叉链表中的空指针改为指向遍历前驱或遍历后继的线索。
因为遍历前驱和遍历后继的信息只有在遍历时才能够得到,因此线索化的实质就是遍历一次二叉树。
先序线索化
递归实现
/**
* @param *pre 前一个结点
* @param *node 当前结点
*/
void inThread(ThreadNode *node, ThreadNode *pre) {
if (node == nullptr)
return;
if (node->lchild == nullptr) {
// 当前结点的左孩子为空,则当前结点的遍历前驱为前一个结点
node->lchild = pre;
node->ltag = true;
}
if (pre != nullptr && pre->rchild == nullptr){
// 前一个结点的右孩子为空,则前一个结点的遍历后继为当前结点
pre->rchild = node;
pre->rtag = true;
}
pre = node;
inThread(node->lchild, pre);
inThread(node->rchild, pre);
}
中序线索化
递归实现
// C++
/**
* @param *pre 前一个结点
* @param *node 当前结点
*/
void inThread(ThreadNode *node, ThreadNode *pre) {
if (node == nullptr)
return;
inThread(node->lchild, pre);
if (node->lchild == nullptr) {
node->lchild = pre;
node->ltag = true;
}
if (pre != nullptr && pre->rchild == nullptr){
pre->rchild = node;
pre->rtag = true;
}
pre = node;
inThread(node->rchild, pre);
}
后续线索化
递归实现
/**
* @param *pre 前一个结点
* @param *node 当前结点
*/
void inThread(ThreadNode *node, ThreadNode *pre) {
if (node == nullptr)
return;
inThread(node->lchild, pre);
inThread(node->rchild, pre);
if (node->lchild == nullptr) {
node->lchild = pre;
node->ltag = true;
}
if (pre != nullptr && pre->rchild == nullptr){
pre->rchild = node;
pre->rtag = true;
}
pre = node;
}
线索二叉树的遍历
有序线索二叉树中包含了线索,因此,我们只需要按照线索进行遍历即可。下面给出不带头结点的线索二叉树的遍历。
先序线索二叉树的遍历
void foreach(ThreadNode *node) {
ThreadNode *p = node;
while (p != nullptr) {
visit(node);
// 有左孩子,左孩子是后继
if(!p->ltag) p = p->lchild;
// 无左孩子有右孩子,右孩子是后继
else if(!p->rtag) p = p->rchild;
// 叶结点的右孩子是线索,指向后继
else p = p->rchild;
}
}
中序线索二叉树的遍历
void foreach(ThreadNode *node) {
ThreadNode *p = node;
while (p != nullptr) {
while(!p->ltag) p = p->lchild;
visit(p);
while(p->rtag) {
p = p->rchild;
visit(p);
}
p=p->rchild;
}
}
后续线索二叉树的遍历
后续线索二叉树中查找结点的后继较为复杂,有以下几种情况:
- 若结点x是二叉树的根,则后继为空
- 若结点x是其双亲的右孩子,则其后继为双亲结点。
- 若结点x是其双亲的左孩子,且其双亲没有右孩子,则其后继为双亲结点。
- 若结点x是其双亲的左孩子,且其双亲有右子树,则其后继为右子树上按后续遍历列出的第一个结点。
依据4,我们可以知道,对于有左右孩子的双亲结点来说,它的后继可能无法直接找到。因此如果在后续线索二叉树上查找后继时,还需要知道双亲结点。我们可以采用带标志域的三叉链表作为存储结构、或者使用栈来辅助后续线索二叉树的遍历
class ThreadNode {
public:
// 这里省略数据域
// parent指向该节点的双亲结点,需要在新增结点时指定
ThreadNode *lchild, *rchild, *parent;
bool ltag, rtag;
};
后续线索二叉树的遍历算法待补充
未完待续