所谓线索二叉树,即按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排序为一个线性序列。在改序列中,除第一个结点外每个结点有且仅有一个直接前驱结点;除最后一个结点外每一个结点有且仅有一个直接后继结点。这些指向直接前驱结点和指向直接后续结点的指针被称为线索(Thread),加了线索的二叉树称为线索二叉树。
当用二叉链表作为二叉树的存储结构时,因为每个结点中只有指向其左、右儿子结点的指针,所以从任一结点出发只能直接找到该结点的左、右儿子。在一般情况下靠它无法直接找到该结点在某种遍历序下的前驱和后继结点。如果在每个结点中增加指向其前驱和后继结点的指针,将降低存储空间的效率。
我们可以证明:在n个结点的二叉链表中含有n+1个空指针。因为含n个结点的二叉链表中含有个指针,除了根结点,每个结点都有一个从父结点指向该结点的指针,因此一共使用了n-1个指针,所以在n个结点的二叉链表中含有n+1个空指针。
因此可以利用这些空指针,存放指向结点在某种遍历次序下的前驱和后继结点的指针。这种附加的指针称为线索,加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(ThreadedBinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
线索二叉树-结构
例如图(a)是一棵中序线索二叉树,它的线索链表如图(b)所示。
(a)
(b)
图(b)中,在二叉树的线索链表上增加了一个头结点,其LeftChild指针指向二叉树的根结点,其RightChild指针指向中序遍历时的最后一个结点。另外,二叉树中依中序列表的第一个结点的LeftChild指针,和最后一个结点的RightChild指针都指向头结点。这就像为二叉树建立了一个双向线索链表,既可从第一个结点起,顺着后继进行遍历,也可从最后一个结点起顺着前驱进行遍历。
如何在线索二叉树中找结点的前驱和后继结点?以图13的中序线索二叉树为例。树中所有叶结点的右链是线索,因此叶结点的RightChild指向该结点的后继结点,如图13中结点"b"的后继为结点"*"。当一个内部结点右线索标志为0时,其RightChild指针指向其右儿子,因此无法由RightChild得到其后继结点。然而,由中序遍历的定义可知,该结点的后继应是遍历其右子树时访问的第一个结点,即右子树中最左下的结点。例如在找结点"*"的后继时,首先沿右指针找到其右子树的根结点"-",然后沿其LeftChild指针往下直至其左线索标志为1的结点,即为其后继结点(在图中是结点"c")。类似地,在中序线索树中找结点的前驱结点的规律是:若该结点的左线索标志为1,则LeftChild为线索,直接指向其前驱结点,否则遍历左子树时最后访问的那个结点,即左子树中最右下的结点为其前驱结点。由此可知,若线索二叉树的高度为h,则在最坏情况下,可在O(h)时间内找到一个结点的前驱或后继结点。在对中序线索二叉树进行遍历时,无须像非线索树的遍历那样,利用递归引入栈来保存待访问的子树信息。
对一棵非线索二叉树以某种次序遍历使其变为一棵线索二叉树的过程称为二叉树的线索化。由于线索化的实质是将二叉链表中的空指针改为指向结点前驱或后继的线索,而一个结点的前驱或后继结点的信息只有在遍历时才能得到,因此线索化的过程即为在遍历过程中修改空指针的过程。为了记下遍历过程中访问结点的先后次序,可附设一个指针pre始终指向刚刚访问过的结点。当指针p指向当前访问的结点时,pre指向它的前驱。由此也可推知pre所指结点的后继为p所指的当前结点。这样就可在遍历过程中将二叉树线索化。对于找前驱和后继结点这二种运算而言,线索树优于非线索树。但线索树也有其缺点。在进行插人和删除操作时,线索树比非线索树的时间开销大。原因在于在线索树中进行插人和删除时,除了修改相应的指针外,还要修改相应的线索。
1、线索二叉树的结点结构
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。现将二叉树的结点结构重新定义如下:
lchild | ltag | data | rtag | rchild |
其中:ltag=0 时 lchild指向左子女;
ltag=1 时 lchild指向前驱;
rtag=0 时 rchild指向左子女;
rtag=1 时 rchild指向后继;
以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,指向前驱和后继的指针叫线索,加上线索的二叉树叫线索二叉树,对二叉树进行某种形式遍历使其变为线索二叉树的过程叫线索化。
学习线索化时,有三点必须注意:一是何种“序”的线索化,是先序、中序还是后序;二是要“前驱”线索化、“后继”线索化还是“全”线索化(前驱后继都要);三是只有空指针处才能加线索。
2、对二叉树进行中序线索化的算法
bithptr *pre; /* 全程变量 */
void INTHREAD(bithptr *p)
{if(p!=NULL)
{ INTHREAD(p->lchild); /* 左子树线索化 */
if(p->lchild==NULL) { p->ltag=1;p->lchild=pre;}
if(p->rchild==NULL) p->rtag=1;
if(pre!=NULL && pre->rtag==1) pre->rchild=p;
pre=p; /* 前驱指向当前结点 */
INTHREAD(p->rchild); /* 右子树线索化 */
}
3、在线索二叉树上查找前驱和后继
(1)中序线索二叉树:若结点的ltag=1,lchild指向其前驱;否则,该结点的前驱是以该结点为根的左子树上按中序遍历的最后一个结点。若rtag=1,rchild指向其后继;否则,该结点的后驱是以该结点为根的右子树上按中序遍历的第一个结点。
求后继的算法如下:
bithptr *INORDERNEXT(bithptr *p)
{if (p->rtag==1) return(p->rchild);
else {q=p->rchild; /* 找右子树最先访问的结点 */
while (q->ltag==0) q=q->lchild;
return(q);
}
}
求前驱的算法如下:
bithptr *INORDERNEXT(bithptr *p)
{if (p->ltag==1) return(p->lchild);
else {q=p->lchild;/* 找左子树最后访问的结点 */
while (q->rtag==0) q=q->rchild;
return(q);
}
}
(2) 后序线索二叉树:
在后序线索二叉树中查找结点*p的前驱:若结点*p无左子树,则p->lchild指向其前驱;否则,若结点*p有左子树,当其右子树为空时,其左子树的根(即p->lrchild)为其后序前驱。当其右子树非空时,其右子树的根(即p->rchild)为其后序前驱。
在后序线索二叉树中查找结点*p的后继:若结点*p为根,则无后继;若结点*p为其双亲的右孩子,则其后继为其双亲;若结点*p为其双亲的左孩子,且双亲无右子女,则其后继为其双亲;若结点*p为其双亲的左孩子,且双亲有右子女,则结点*p的后继是其双亲的右子树中按后序遍历的第一个结点。所以,求后序线索二叉树中结点的后继要知道其双亲的信息,要使用栈,所以说后序线索二叉树是不完善的。
(3)先序线索二叉树:
在先序线索二叉树中查找结点的后继较容易,而查找前驱要知道其双亲的信息,要使用栈,所以说先序线索二叉树也是不完善的。
4、对线索二叉树进行中序遍历的算法
void TRAVERSEINTHREAD(bithptr *p)
{if (p!=NULL)
{ while(p->ltag==0) p=p->lchild;
do { visit(p->data);
p=INORDERNEXT(p);
} while (p!=NULL)
}
}
2、 线索二叉树的插入
在线索二叉树上插入结点,不仅要修改结点的指针,还要修改线索。现仅以在中序线索二叉树的*p结点和其右孩子结点之间,插入*q(它只有右子树)结点讨论之。
void INSERTRIGHT(bithptr *p,*q)
{bithptr *s;
s=INORDERNEXT(p);
q->ltag=1;q->lchild=p;q->rtag=p->rtag;q->rchild=p->rchild;
p->rtag=0;p->rchild=q;
if (s!=null && s->ltag==1) s->lchild=q;
}
以下转载程序
具体示例:#include<stdio.h>
#include<stdlib.h>
typedef struct Btreenode
{
struct Btreenode *lchild,*rchild;
char data;
}Bitreenode,*Bitree;
//初始化
Bitree Initree()
{
Bitree p;
p=(Bitreenode *)malloc(sizeof(Bitreenode));
p->lchild=p->rchild=NULL;
return p;
}
//创建二叉树 ABD#GJ##K##E##C#FH##IL### ABCD##E###F#GH##I#J#K##
void Creat(Bitree *p) //指向指针的指针
{
char ch=getchar();
if(ch=='#')
(*p)=NULL;
else
{
//Bitree k;
*p=(Bitreenode *)malloc(sizeof(Bitreenode));
(*p)->data=ch;
Creat(&(*p)->lchild);
Creat(&(*p)->rchild);
}
}
//先序遍历
void First(Bitree p)
{
if(p)
{
printf("%2c",p->data);
First(p->lchild);
First(p->rchild);
}
}
//中序遍历
void Middle(Bitree p)
{
if(p)
{
Middle(p->lchild);
printf("%2c",p->data);
Middle(p->rchild);
}
}
//后序遍历
void Last(Bitree p)
{
if(p)
{
Last(p->lchild);
Last(p->rchild);
printf("%2c",p->data);
}
}
//求二叉树的深度
//若一棵二叉树为空,则其深度为0;
//否则其深度等于左子树和右子树的最大深度加1
int DeepBitree(Bitree p)
{
int deep1,deep2;
if(p==NULL)return(0);
else
{ deep1=DeepBitree(p->lchild);
deep2=DeepBitree(p->rchild);
if(deep1>deep2)return(deep1+1);
else return(deep2+1);
}
}
//在二叉树中查找值为x的结点
void search(Bitree *q,char x,Bitree *p)
{
if((*q)->data==x) *p=*q;
else
{
if((*q)->lchild) search(&(*q)->lchild,x,&(*p));
if((*q)->rchild) search(&(*q)->rchild,x,&(*p));
}
}
//求叶子结点数目
int k=0;
void leaf(Bitree p)
{
if(p)
{
leaf(p->lchild);
if(!p->lchild && !p->rchild) k++;
leaf(p->rchild);
}
}
//删除左子树
void Search_lchild(Bitree *p,char parents,Bitree *k)
{
if ((*p)->data==parents)
{
if((*p)->lchild)
{
*k= (*p)->lchild ; //查找左子树
//Deletel(*k);
}
}
else
{
if((*p)->lchild) Search_lchild(&(*p)->lchild,parents,&(*k));
if((*p)->rchild) Search_lchild(&(*p)->rchild,parents,&(*k));
}
}
void Deletel(Bitree *p)
{
Bitree *t;
t=p;
if((*t))
{
Deletel(&(*t)->lchild);
Deletel(&(*t)->rchild);
free(*t);
//(*p)=NULL;
}
}
//插入左结点
void Insert_lchild(Bitree *p,char childl,char child)
{
Bitree q,k;
if(*p)
{
if((*p)->data==childl) //找到结点
{
q=(Bitree)malloc(sizeof(Bitree));
q->data=child;
if(!(*p)->lchild) //左结点不存在
{
(*p)->lchild=q;
(*p)->lchild->lchild=NULL;
(*p)->lchild->rchild=NULL;
}
else //左结点存在
{
k=(*p)->lchild;
(*p)->lchild=q;
q->lchild=k;
}
}
else
{
Insert_lchild(&(*p)->lchild,childl,child);
Insert_lchild(&(*p)->rchild,childl,child);
}
}
}
int main()
{
printf("请输入二叉树(例如:AB##C##):");
Bitree p=Initree(); //ABCE##F##D##G##
//Bitree p=NULL;
Creat(&p);
//先、中、后序遍历
printf("先序遍历为:");
First(p);
printf("\n");
printf("中序遍历为:");
Middle(p);
printf("\n");
printf("后序遍历为:");
Last(p);
printf("\n");
//求二叉树的深度
int deep=DeepBitree(p);
printf("二叉树的深度为:%d\n",deep);
//输出二叉树
//print(p);
//查找数据元素
char s;
Bitree q=NULL;
printf("输入要查找的元素:");
getchar();
scanf("%c",&s);
/*Bitree T=search(&p,s);
if(T)
printf("要查找的元素为:%c\n",T->data);
else
printf("要查找的元素不存在\n");*/
search(&p,s,&q); //******
if(q)
printf("要查找的元素为:%c\n",q->data);
else
printf("要查找的元素不存在\n");
//求叶子结点的数目
leaf(p);
return 0;
}