线索二叉树
不管二叉树的形态如何,空链域的个数总是多过非空链域的个数。n个结点的二叉链表共有2n个链域,非空链域为n-1个,空链域却有n+1个。以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,不能直接得到结点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到。若程序中所用二叉树需经常遍历或查找结点在遍历所得线性序列中的前驱和后继,则应采用线索链表作存储结构。
利用原来的空链域存放指针,指向结点前驱和后继的指针称为线索。
ptr指向二叉链表中的一个结点,以下是建立线索的规则:
(1)如果ptr->lchild为空,则lchild域指示其前驱结点。
(2)如果ptr->rchild为空,则rchild域指示后继结点
为每个结点增设两个标志域LTag和RTag,注意LTag和RTag只是区分0或1数字的布尔型变量,其占用内存空间要小于像lchild和rchild的指针变量。结点结构如下所示。
其中:
ltag为0时指向该结点的左孩子,为1时指向该结点的前驱;
rtag为0时指向该结点的右孩子,为1时指向该结点的后继;
以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表。
线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继信息只有在遍历该二叉树时才能得到,所以,线索化的过程就是在遍历的过程中修改空指针的过程。
相关代码
/* 二叉树的二叉线索存储结构定义*/
typedef char TElemType;
//中序线索二叉树
typedef enum PointerTag {Link, Thread};//结点的child域类型,link表示是指针,指向孩子结点,thread表示是线索,指示前驱或后继结点
//定义结点数据结构
typedef struct ThrBiNode{
TElemType data;
ThrBiNode *lchild, *rchild;//左右孩子指针
PointerTag lTag, rTag;//左右标志
}ThrBiNode, *ThrBiTree;
中序遍历构造线索二叉树的递归函数代码如下:
//中序遍历进行中序线索化
ThrBiTree pre=NULL;
void inThreading(ThrBiTree T){
//pre始终指向刚刚访问过的结点
if(T){
inThreading(T->lchild);//左子树线索化
if(!T->lchild){//当前结点的左孩子为空
T->lTag = Thread;
T->lchild = pre;
}else{
T->lTag = Link;
}
if(!pre->rchild){//前驱结点的右孩子为空
pre->rTag = Thread;
pre->rchild = T;
}else{
pre->rTag = Link;
}
pre = T;
inThreading(T->rchild);//右子树线索化
}
}
/*---------带头结点的线索二叉树遍历--------*/
/*有了线索二叉树后,对它进行遍历时,其实就等于操作一个双向链表结构。
和双向链表结点一样,在二叉树链表上添加一个头结点,并令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历访问时的最后一个结点。反之,令二叉树的中序序列中第一个结点中,lchild域指针和最后一个结点的rchild域指针均指向头结点。这样的好处是:我们既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。*/
void inOrderTraversePrint(ThrBiTree T){
//T指向头结点,头结点的lchild链域指针指向二叉树的根结点
ThrBiNode *p = T->lchild; //p指向根结点
while(p != T){ //空树或遍历结束时,p == T
while(p->lTag == Link){
p = p->lchild;
}
//此时p指向中序遍历序列的第一个结点(最左下的结点),从该结点起顺后继进行遍历
printf("%c ", p->data);//打印(访问)其左子树为空的结点
while(p->rTag == Thread && p->rchild != T){
p = p->rchild;
printf("%c ", p->data);//访问后继结点
}
//当p所指结点的rchild指向的是孩子结点而不是线索时,p的后继应该是其右子树的最左下的结点,即遍历其右子树时访问的第一个节点
p = p->rchild;
}
printf("\n");
}