采用二叉链表作为二叉树的存储结构,只能找到结点的左、右孩子结点,而不能直接找到该结点的直接前驱和后继结点信息,这种信息只能在对二叉树的遍历过程中才能找到,显然这不是最直接、最简便的方法。为了能快速找到任何一个结点的直接前驱和后继结点信息,需要对二叉树进行线索化。
1.线索二叉树的概念
为了能快速找到任何一个结点的直接前驱和后继结点信息,可以在二叉链表结点中增加两个指针域,一个用来指示结点的前驱, 另一个用来指向结点的后继。如果这样做,需要为每个结点增加两个指针域,一个用来指示结点的前驱,另一个用来指向结点的后继。但这样做需要为每个结点增加两个域的存储单元,也会使结点结构的利用率大大下降。
在二叉链表的存储结构中,n个结点的二叉链表需要2n个指针域,其中只有n-1个用来表示结点的左右子树,其余n+1个指针域为空。为了不浪费存储空间,设法把这些空链域利用起来,存储结点的直接前驱和直接后继的信息。
假定若结点存在左子树,则指针域lchild指示其左孩子结点,否则指针域lchild指示其直接前驱结点;若结点存在右子树,则指针域rchild指示右孩子结点,否则指针域lchild指示其直接后继结点。
另外增加两个标志域ltag和rtag,分别用来区分指针域指向的是左孩子结点还是直接前驱结点,右孩子结点还是直接后继结点。当ltag=0时,lchild指示结点的左孩子;当ltag=1时,lchild指示结点的直接前驱结点。当rtag=0时,rchild指示结点的右孩子;当rtag=1时,rchild指示结点的直接后继结点。
由这种存储结构构成的二叉链表称为二叉树的线索二叉树,采用这种存储结构的二叉链表称为线索链表。其中,指向结点直接前驱和直接后继的指针称为线索。二叉树按照某种遍历方式使二叉树变为线索二叉树的过程称为二叉树的线索化。
线索二叉树的存储结构类型描述为:
typedef char DataType;
typedef enum {Link,Thread}PointerTag;
typedef struct Node/*结点类型*/
{
DataType data;
struct Node *lchild, *rchild; /*左右孩子子树*/
PointerTag ltag,rtag; /*线索标志域*/
}BiThrNode;
typedef BiThrNode *BiThrTree; /*二叉树类型*/
2.线索化二叉树
在二叉树的遍历过程中,可得到结点的前驱信息和后继信息,同时将结点的空指针域修改为其直接前驱或直接后继信息。因此二叉树的线索化就是对二叉树的遍历过程。下面以中序线索化为例介绍二叉树的线索化。
为了便于算法操作,在二叉链表中增加一个头结点,头结点的数据域可以存放二叉树的结点信息,也可以为空。令头结点的指针域lchild指向二叉树的根结点,指针域rchild指向二叉树中序遍历时的最后一个结点。在初始化时,使二叉树的头结点指针域rchild指向头结点自身,并将头结点的标志域ltag置为Link,标志域rtag置为Thread。
线索化后的二叉树像一个循环链表,既可以从线索二叉树的第一个结点出发沿着结点的后继线索指针遍历整个二叉树,也可以从线索二叉树中的第一个结点出发沿着结点的后继线索指针遍历整个二叉树,也可以从线索二叉树的最后一个结点出发沿着结点的前驱线索指针遍历整个二叉树。
中序线索二叉树的算法实现如下:
BiThrTree pre;
int InOrderThreading(BiThrTree *Thrt,BiThrTree T)
/*通过中序遍历二叉树T,使T中序线索化。Thrt是指向头结点的指针*/
{
if(!(*Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) /*为头结点分配内存单元*/
exit(-1);
/*将头结点线索化*/
(*Thrt)->ltag=Link; /*修改前驱线索标志*/
(*Thrt)->rtag=Thread; /*修改后继线索标志*/
(*Thrt)->rchild=*Thrt; /*将头结点的rchild指针指向自己*/
if(!T) /*如果二叉树为空,则将lchild指针指向自己*/
(*Thrt)->lchild=*Thrt;
else
{
(*Thrt)->lchild=T; /*将头结点的左指针指向根结点*/
pre=*Thrt; /*将pre指向已经线索化的结点*/
InThreading(T); /*中序遍历进行中序线索化*/
/*将最后一个结点线索化*/
pre->rchild=*Thrt; /*将最后一个结点的右指针指向头结点*/
pre->rtag=Thread; /*修改最后一个结点的rtag标志域*/
(*Thrt)->rchild=pre; /*将头结点的rchild指针指向最后一个结点*/
}
return 1;
}
void InThreading(BiThrTree p)
/*二叉树中序线索化*/
{
if(p)
{
InThreading(p->lchild); /*左子树线索化*/
if(!p->lchild) /*前驱线索化*/
{
p->ltag=Thread;
p->lchild=pre;
}
if(!pre->rchild) /*后继线索化*/
{
pre->rtag=Thread;
pre->rchild=p;
}
pre=p; /*pre指向的结点线索化完毕,使p指向的结点成为前驱*/
InThreading(p->rchild); /*右子树线索化*/
}
}
3.遍历线索二叉树
遍历线索二叉树就是根据线索查找结点的前驱和后继。
在中序线索二叉树中查找结点的直接前驱
在中序线索二叉树中,结点*p的直接前驱就是其左子树的最右下端结点。若p->ltag=1,那么p->lchild指向的结点就是p的直接前驱结点。
BiThrNode *InOrderPre(BiThrNode *p)
/*在中序线索树中找结点*p的中序直接前趋*/
{
BiThrNode *pre;
if (p->ltag==Thread) /*如果p的标志域ltag为线索,则p的左子树结点即为前驱*/
return p->lchild;
else{
pre=p->lchild; /*查找p的左孩子的最右下端结点*/
while (pre->rtag==Link) /*右子树非空时,沿右链往下查找*/
pre=pre->rchild;
return pre; /*pre就是最右下端结点*/
}
}
在中序线索二叉树中查找结点的直接后继
在中序线索二叉树中,查找结点*p的中序直接后继与查找结点的直接前驱类似。若p->rtag=1,那么p->rchild指向的结点就是p的直接后继结点。
BiThrNode *InOrderPost(BiThrNode *p)
/*在中序线索树中查找结点*p的中序直接后继*/
{
BiThrNode *pre;
if (p->rtag==Thread) /*如果p的标志域ltag为线索,则p的右子树结点即为后继*/
return p->rchild;
else
{
pre=p->rchild; /*查找p的右孩子的最左下端结点*/
while (pre->ltag==Link) /*左子树非空时,沿左链往下查找*/
pre=pre->lchild;
return pre; /*pre就是最左下端结点*/
}
}
中序遍历线索二叉树
中序遍历线索二叉树可分为3步:
- 从根结点出发,找到二叉树的最左下端结点并访问;
- 判断该结点的右标志域是否为线索指针,若为线索指针即p->rtag=Thread,表明p->rchild指向的是后继节点,则将指针移动到右孩子结点;
- 将当前指针指向该右孩子结点。
重复以上3步,就可访问完二叉树中的所有结点。中序遍历线索二叉树的过程就是线索查找后继和查找右子树的最左下端结点的过程。
int InOrderTraverse(BiThrTree T,int (* visit)(BiThrTree e))
/*中序遍历线索二叉树。其中visit是函数指针,指向访问结点的函数实现*/
{
BiThrTree p;
p=T->lchild; /*p指向根结点*/
while(p!=T) /*空树或遍历结束时,p==T*/
{
while(p->ltag==Link)
p=p->lchild;
if(!visit(p)) /*打印*/
return 0;
while(p->rtag==Thread&&p->rchild!=T) /*访问后继结点*/
{
p=p->rchild;
visit(p);
}
p=p->rchild;
}
return 1;
}
结论:对于中序线索二叉树,若ltag=0,则直接前驱为左子树的最右下端结点;若rtag=0,则其直接后继为右子树的最左下端结点。
在后续线索二叉树中查找后继结点
在后续线索二叉树中查找后继结点较复杂些,分为3种情况:若结点x是二叉树的根,则其后继为空;若结点x是其双亲结点的有孩子或者是双亲结点的左孩子且双亲没有有孩子,则其后继即为双亲结点;若结点x是其双亲结点的左孩子,且其双亲有右孩子,则其后继为双亲的右子树上按后序遍历得到的第一个结点。
4.线索二叉树应用示例
创建如下图所示的二叉树,并将其中序线索化。任给一个结点,要求查找该结点的直接前驱和直接后继。例如,结点F的直接前驱是A,其直接后继是I。
#include <stdio.h>
#include <malloc.h>
#include<stdlib.h>
#define MaxSize 100
/*线索二叉树类型定义*/
typedef char DataType;
typedef enum {Link,Thread}PointerTag;
typedef struct Node/*结点类型*/
{
DataType data;
struct Node *lchild, *rchild; /*左右孩子子树*/
PointerTag ltag,rtag; /*线索标志域*/
}BiThrNode;
typedef BiThrNode *BiThrTree; /*二叉树类型*/
/*函数声明*/
void CreateBitTree2(BiThrTree *T,char str[]); /*创建线索二叉树*/
void InThreading(BiThrTree p); /*中序线索化二叉树*/
int InOrderThreading(BiThrTree *Thrt,BiThrTree T); /*通过中序遍历二叉树T,使T中序线索化。Thrt是指向头结点的指针*/
int InOrderTraverse(BiThrTree T,int (* visit)(BiThrTree e)); /*中序遍历线索二叉树*/
int Print(BiThrTree T); /*打印二叉树中的结点及线索标志*/
BiThrNode *FindPoint(BiThrTree T,DataType e); /*在线索二叉树中查找结点为e的指针*/
BiThrNode *InOrderPre(BiThrNode *p); /*查找中序线索二叉树的中序前驱*/
BiThrNode *InOrderPost(BiThrNode *p); /*查找中序线索二叉树的中序后继*/
BiThrTree pre; /*pre始终指向已经线索化的结点*/
void DestroyBitTree(BiThrTree *T);/*销毁线索二叉树*/
void main()
/*测试程序*/
{
DataType ch;
BiThrTree T,Thrt;
BiThrNode *p,*pre,*post;
CreateBitTree2(&T,"(A(B(D,E(H)),C(F(,I),G)))");
printf("输出线索二叉树的结点、前驱及后继信息:\n");
InOrderThreading(&Thrt,T);
printf("序列 前驱标志 结点 后继标志\n");
InOrderTraverse(Thrt,Print);
printf("请输入要查找哪个结点的前驱和后继:");
ch=getchar();
p=FindPoint(Thrt,ch);
pre=InOrderPre(p);
printf("元素%c的中序直接前驱元素是:%c\n",ch,pre->data);
post=InOrderPost(p);
printf("元素%c的中序直接后继元素是:%c\n",ch,post->data);
DestroyBitTree(&Thrt);
}
int Print(BiThrTree T)
/*打印线索二叉树中的结点及线索*/
{
static int k=1;
printf("%2d\t%s\t %2c\t %s\t\n",k++,T->ltag==0?"Link":"Thread",
T->data,
T->rtag==1?"Thread":"Link");
return 1;
}
void DestroyBitTree(BiThrTree *T)
/*销毁二叉树*/
{
if(*T) /*如果是非空二叉树*/
{
if((*T)->lchild)
DestroyBitTree(&((*T)->lchild));
if((*T)->rchild)
DestroyBitTree(&((*T)->rchild));
free(*T);
*T=NULL;
}
}
void CreateBitTree2(BiThrTree *T,char str[])
/*利用括号嵌套的字符串建立二叉链表*/
{
char ch;
BiThrTree stack[MaxSize]; /*定义栈,用于存放指向二叉树中结点的指针*/
int top=-1; /*初始化栈顶指针*/
int flag,k;
BiThrNode *p;
*T=NULL,k=0;
ch=str[k];
while(ch!='\0') /*如果字符串没有结束*/
{
switch(ch)
{
case '(':
stack[++top]=p;
flag=1;
break;
case ')':
top--;
break;
case ',':
flag=2;
break;
default:
p=(BiThrTree)malloc(sizeof(BiThrNode));
p->data=ch;
p->lchild=NULL;
p->rchild=NULL;
if(*T==NULL) /*如果是第一个结点,表示是根结点*/
*T=p;
else
{
switch(flag)
{
case 1:
stack[top]->lchild=p;
break;
case 2:
stack[top]->rchild=p;
break;
}
if(stack[top]->lchild)
stack[top]->ltag=Link;
if(stack[top]->rchild)
stack[top]->rtag=Link;
}
}
ch=str[++k];
}
}
BiThrNode *FindPoint(BiThrTree T,DataType e)
/*中序遍历线索二叉树,返回元素值为e的结点的指针。*/
{
BiThrTree p;
p=T->lchild; /*p指向根结点*/
while(p!=T) /*如果不是空二叉树*/
{
while(p->ltag==Link)
p=p->lchild;
if(p->data==e) /*找到结点,返回指针*/
return p;
while(p->rtag==Thread&&p->rchild!=T) /*访问后继结点*/
{
p=p->rchild;
if(p->data==e) /*找到结点,返回指针*/
return p;
}
p=p->rchild;
}
return NULL;
}
BiThrNode *InOrderPre(BiThrNode *p)
/*在中序线索树中找结点*p的直接前趋*/
{
BiThrNode *pre;
if (p->ltag==Thread) /*如果p的标志域ltag为线索,则p的左子树结点即为前驱*/
return p->lchild;
else
{
pre=p->lchild; /*查找p的左孩子的最右下端结点*/
while (pre->rtag==Link) /*右子树非空时,沿右链往下查找*/
pre=pre->rchild;
return pre; /*pre就是最右下端结点*/
}
}
BiThrNode *InOrderPost(BiThrNode *p)
/*在中序线索树中查找结点*p的直接后继*/
{
BiThrNode *pre;
if (p->rtag==Thread) /*如果p的标志域ltag为线索,则p的右子树结点即为后继*/
return p->rchild;
else
{
pre=p->rchild; /*查找p的右孩子的最左下端结点*/
while (pre->ltag==Link) /*左子树非空时,沿左链往下查找*/
pre=pre->lchild;
return pre; /*pre就是最左下端结点*/
}
}
int InOrderTraverse(BiThrTree T,int (* visit)(BiThrTree e))
/*中序遍历线索二叉树。其中visit是函数指针,指向访问结点的函数实现*/
{
BiThrTree p;
p=T->lchild; /*p指向根结点*/
while(p!=T) /*空树或遍历结束时,p==T*/
{
while(p->ltag==Link)
p=p->lchild;
if(!visit(p)) /*打印*/
return 0;
while(p->rtag==Thread&&p->rchild!=T) /*访问后继结点*/
{
p=p->rchild;
visit(p);
}
p=p->rchild;
}
return 1;
}
BiThrTree pre; /*pre始终指向已经线索化的结点*/
int InOrderThreading(BiThrTree *Thrt,BiThrTree T)
/*通过中序遍历二叉树T,使T中序线索化。Thrt是指向头结点的指针*/
{
if(!(*Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) /*为头结点分配内存单元*/
exit(-1);
/*将头结点线索化*/
(*Thrt)->ltag=Link; /*修改前驱线索标志*/
(*Thrt)->rtag=Thread; /*修改后继线索标志*/
(*Thrt)->rchild=*Thrt; /*将头结点的rchild指针指向自己*/
if(!T) /*如果二叉树为空,则将lchild指针指向自己*/
(*Thrt)->lchild=*Thrt;
else
{
(*Thrt)->lchild=T; /*将头结点的左指针指向根结点*/
pre=*Thrt; /*将pre指向已经线索化的结点*/
InThreading(T); /*中序遍历进行中序线索化*/
/*将最后一个结点线索化*/
pre->rchild=*Thrt; /*将最后一个结点的右指针指向头结点*/
pre->rtag=Thread; /*修改最后一个结点的rtag标志域*/
(*Thrt)->rchild=pre; /*将头结点的rchild指针指向最后一个结点*/
}
return 1;
}
void InThreading(BiThrTree p)
/*二叉树的中序线索化*/
{
if(p)
{
InThreading(p->lchild); /*左子树线索化*/
if(!p->lchild) /*前驱线索化*/
{
p->ltag=Thread;
p->lchild=pre;
}
if(!pre->rchild) /*后继线索化*/
{
pre->rtag=Thread;
pre->rchild=p;
}
pre=p; /*pre指向的结点线索化完毕,使p指向的结点成为前驱*/
InThreading(p->rchild); /*右子树线索化*/
}
}
- 测试结果