线索二叉树
线索二叉树的概念
由于传统的二叉树链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继,于是引入了线索二叉树。
遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。引入线索二叉树正是为了加快查找结点的前驱和后继的速度。
线索二叉树的存储结构描述如下:
typedef struct ThreadNode {
ElemType data;
ThreadNode* lchild, * rchild;
int ltag, rtag;
}ThreadNode, * ThreadTree;
中序线索二叉树的构造
二叉树的线索化是将二叉链表中的空指针改为指向前驱或后继的线索。而前驱或后继的信息只有在遍历时才能看到,因此线索化的是指就是遍历一个二叉树。
以中序线索二叉树为例。附设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,既pre指向p的前驱。在中序遍历的过程中,检查p的左指针是否为空,若为空就将它指向pre;检查pre的右指针是否为空,若为空就将它指向p。
通过中序遍历对二叉树线索化的递归算法如下:
void InThread(ThreadTree& p, ThreadTree& pre) { //pre初始为空树
if (p != NULL) {
InThread(p->lchild, pre); //递归,线索化左子树
if (p->lchild == NULL) { //左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 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->rtag = 1;
}
}
中序线索二叉树的遍历
中序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找到结点的后继,直至其后继为空。
在中序线索二叉树中找结点后继的规律是:若其标志为1,则右链为线索,指示其后继。若其标记为“0”,遍历右子树中第一个访问的结点(右子树中最左下的结点)为其后继。
具体算法如下:
ThreadNode* Firstnode(ThreadNode* p) {
while (p->ltag == 1) {
p = p->lchild;
}
return p;
}
ThreadNode* Nextnode(ThreadNode* p) {
if (p->rtag == 1) {
return Firstnode(p->rchild);
}
else {
return p->rchild;
}
}
先序线索二叉树和后序线索二叉树
上面给出了建立中序线索二叉树的代码,建立先序线索二叉树和后序线索二叉树的代码类似,只需要改动线索化改造的代码段与调用线索化左右子树递归函数的位置。
在先序线索二叉树中找结点的后继
如果有左孩子,则左孩子就是后继;如果无左孩子但有右孩子,则右孩子就是其后继;如果为叶结点,则右链域直接指示了结点的后继。
在后序线索二叉树中找结点的后继
① 若结点i是二叉树的根,则其后继为空。
② 若结点i是其双亲的右孩子,或其双亲的左孩子且其双亲没有右子树,则其后继为双亲。
③ 若结点i是其双亲的左孩子,且其双亲有右子树,则其后继为双亲的右子树按后序遍历列出的第一个结点。