线索二叉树
Posted 牧空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线索二叉树相关的知识,希望对你有一定的参考价值。
基本概念
遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。
传统的二叉链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继。前面提到,在含n个结点的二叉树中,有 n + 1 n+1 n+1个空指针。这是因为每个叶结点有2个空指针,每个度为1的结点有1个空指针,空指针总数为 2 n 0 + n 1 2n_0+ n_1 2n0+n1,又 n 0 = n 2 + 1 n_0 = n_2+ 1 n0=n2+1,所以空指针总数为 n 0 + n 1 + n 2 + 1 = n + 1 n_0 + n_1 + n_2 + 1=n +1 n0+n1+n2+1=n+1。由此设想能否利用这些空指针来存放指向其前驱或后继的指针?这样就可以像遍历单链表那样方便地遍历二叉树。引入线索二叉树正是为了加快查找结点前驱和后继的速度。
规定:若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。
需增加两个标志域标识指针域是指向左(右)孩子还是指向前驱(后继)。
结点数据结构
ltag | rtag | |
---|---|---|
0 | lchild域指示结点的左孩子 | rchild域指示结点的右孩子 |
1 | lchild域指示结点的前驱 | rchild域指示结点的前驱 |
中序线索二叉树的构造
二叉树的线索化是将二叉链表中的空指针改为指向前驱或后继的线索。而前驱或后继的信息只有在遍历时才能得到,因此线索化的实质就是遍历一次二叉树。
以中序线索二叉树的建立为例。附设指针pre指向刚刚访问过的结点,指针 p 指向正在访问的结点,即 pre 指向p 的前驱。在中序遍历的过程中,检查p的左指针是否为空,若为空就将它指向pre;检查pre的右指针是否为空,若为空就将它指向p。
中序遍历对二叉树线索化的递归算法
void CreateInThread(ThreadTree T){
ThreadTree pre = NULL;
if(T!=NULL){
InThread(T,pre); // 非空二叉树,线索化
pre->rchild=NULL; // 线索化二叉树
pre->rtag=1; // 处理遍历的最后一个结点
}
}
void InThread(ThreadTree &p, ThreadTree &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);// 递归,线索化右子树
} // if(p!=NULL)
}
为了方便,可以在二叉树的线索链表上也添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点;令二叉树中序序列中的第一个结点的lchild域指针和最后一个结点的rchild域指针均指向头结点。这好比为二叉树建立了一个双向线索链表,方便从前往后或从后往前对线索二叉树进行遍历,如图5.12所示。
中序线索二叉树的遍历
- 求中序线索二叉树中中序序列下的第一个结点
ThreadNode *Firstnode(ThreadNode *p){
while(p->ltag==0)
p=p->lchild;// 最左下结点(不一定是叶节点)
return p;
}
- 求中序线索二叉树中结点p在中序序列下的后续
ThreadNode *Nextnode(ThreadNode *p){
if(p->rtag==0)
return Firstnode(p->rchild);
else
return p->rchild; // rtag==1 直接返回后继线索
}
- 利用上面两个算法,可以写出不含头结点的中序线索二叉树的中序遍历的算法
void Inorder(ThreadNode *T){
for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))
visit(p);
}
先序线索二叉树和后序二叉树
上面给出了建立中序线索二叉树的代码,建立先序线索二叉树和后序线索二叉树的代码类似,只需变动线索化改造的代码段与调用线索化左右子树递归函数的位置。
以图5.13(a)的二叉树为例给出手动求先序线索二叉树的过程:先序序列为ABC DF,然后依次判断每个结点的左右链域,如果为空则将其改造为线索。结点A,B均有左右孩子;结点C无左孩子,将左链域指向前驱B,无右孩子,将右链域指向后继D;结点D无左孩子,将左链域指向前驱C,无右孩子,将右链域指向后继F;结点F无左孩子,将左链域指向前驱D,无右孩子,也无后继故置空,得到的先序线索二叉树如图5.13(b)所示。求后序线索二叉树的过程:后序序列为CDBF A,结点C无左孩子,也无前驱故置空,无右孩子,将右链域指向后继D;结点D无左孩子,将左链域指向前驱C,无右孩子,将右链域指向后继B;结点F无左孩子,将左链域指向前驱B,无右孩子,将右链域指向后继A,得到的后序线索二叉树如图5.13©所示。
如何在先序线索二叉树中找结点的后继﹖如果有左孩子,则左孩子就是其后继;如果无左孩子但有右孩子,则右孩子就是其后继;如果为叶结点,则右链域直接指示了结点的后继。
在后序线索二叉树中找结点的后继较为复杂,可分3种情况:①若结点x是二叉树的根,则其后继为空;②若结点x是其双亲的右孩子,或是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲;③若结点x是其双亲的左孩子,且其双亲有右子树,则其后继为双亲的右子树上按后序遍历列出的第一个结点。图5.13©中找结点B的后继无法通过链域找到,可见在后序线索二叉树上找后继时需知道结点双亲,即需采用带标志域的三叉链表作为存储结构。
以上是关于线索二叉树的主要内容,如果未能解决你的问题,请参考以下文章