摘要
按照某种遍历方式对二叉树进行遍历,可以把
二叉树中所有结点排序为一个线性序列。在该序列中,除第一个结点外每个结点有且仅有一个直接前驱结点;除最后一个结点外每一个结点有且仅有一个直接后继结点。这些指向直接前驱结点和指向直接后续结点的指针被称为线索(Thread),加了线索的
二叉树称为线索二叉树。
n个结点的二叉链表中含有n+1(2n-(n-1)=n+1)个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为"
线索")。
这种加上了线索的二叉链表称为
线索链表,相应的二叉树称为
线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为
前序
线索二叉树、中序线索二叉树和后序线索二叉树三种。
注意:
线索链表解决了无法直接找到该结点在某种遍历序列中的前趋和后继结点的问题,出现了二叉链表找左、右孩子困难的问题。
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;
二是利用二叉树的空链指针。现将二叉树的结点结构重新定义如下:
lchild
|
ltag
|
data
|
rtag
|
rchild
|
其中:ltag=0 时lchild指向左子女;
ltag=1 时lchild指向前驱;
rtag=0 时rchild指向右子女;
rtag=1 时rchild指向后继;
建立线索二叉树,或者说对二叉树线索化,实质上就是遍历一颗二叉树。
在遍历过程中,访问结点的操作是检查当前的左,右指针域是否为空,将它们改为指向前驱结点或后续结点的线索。为实现这一过程,设指针pre始终指向刚刚访问的结点,即若指针p指向当前结点,则pre指向它的前驱,以便设线索。
另外,在对一颗二叉树加线索时,必须首先申请一个头结点,建立头结点与二叉树的根结点的指向关系,对二叉树线索化后,还需建立最后一个结点与头结点之间的线索。
下面是建立中序二叉树的
递归算法,其中pre为全局变量。
BiThrNodeType *pre;
BiThrTree InOrderThr(BiThrTree T)
{ /*中序遍历二叉树T,并将其中序线索化,pre为
全局变量*/
BiThrTree head;
head=(BitThrNodeType *)
malloc(
sizeof(BiThrType));/*设申请头结点成功*/
head->ltag=0;head->rtag=1;/*建立头结点*/
head->rchild=head;/*右指针回指*/
if(!T)head->lchild=head;/*若二叉树为空,则左指针回指*/
else{head->lchild=T;pre=head;
InThreading(T);/*中序遍历进行中序线索化*/
pre->rchild=head;
pre->rtag=1;/*最后一个结点线索化*/
head->rchild=pre;
};
return head;
}
void InThreading(BiThrTree p)
{/*通过中序遍历进行中序线索化*/
if(p)
{InThreading(p->lchild);/*左子树线索化*/
if(p->lchild==
NULL)/*前驱线索*/
{p->ltag=1;
p->lchild=pre;
}
else p->ltag=0;
if(p->rchild==NULL)p->rtag=1;/*后驱线索*/
else p->rtag = 0;
if(pre!=NULL && pre->rtag==1) pre->rchild=p;
pre=p;
InThreading(p->rchild);/*右子树线索化*/
}
}
进行中序线索化的算法
bithptr*pre; /* 全程变量*/
voidINTHREAD(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); /* 右子树线索化*/
}
线索二叉树查找前驱和后继
(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)先序线索二叉树:
在先序线索二叉树中查找结点的后继较容易,而查找前驱要知道其双亲的信息,要使用栈,所以说先序线索二叉树也是不完善的。