线索二叉树
二叉树遍历的缺点:当以二叉树链表作为存储结构时,只能找到结点的左右孩子信息,而不能直接得到结点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到。
如何保存这种在遍历过程中得到的信息呢?一个最简单的办法是在每个结点上增加两个指针域fwd和bkwd,分别指示结点在任一次序遍历时得到的前驱和后继信息。
二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树(Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
线索二叉树通过设置LTAG,RTAG标志,并事先线索化来提高整个二叉树的遍历速度,是个典型的牺牲空间来提升速度的例子。线索二叉树方便查找一个结点在访问次序上的前后邻居。
LTag = 0 lchild域指示结点的左孩子
LTag = 1 lchild域指示结点的前驱
RTag = 0 lchild域指示结点的右孩子
RTag = 1 lchild域指示结点的后继
在线索树上进行遍历,只要先找到序列中结点,然后依次找结点后继直至其后继为空时为止。
根据中序遍历的规律可知,结点的后继应是遍历其右子树时访问的第一个结点。即右子树中最左下的结点。在中序线索二叉树上遍历二叉树,虽则时间复杂度亦为O(n),但常数因子要小很多,且不需要设栈。因此,若在某程序中所用二叉树需经常遍历或查找结点在遍历所得线性序列中的前驱和后继,则应采用线索链表作存储结构。
由于线索化的实质是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历时才能得到,因此线索化的过程即为在遍历的过程中修改空指针的过程。
/*Link == 0 指针 Thread == 1 线索*/
typedef enum PointTag {
Link,
Thread
}PointerTag;
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild, *rchild;/*左右孩子指针*/
PointerTag LTag,RTag;/*左右标志*/
}BiThrNode,*BiThrTree;
Status CreateBiThrTree(BiThrTree *T)
{ /* 按先序输入二叉线索树中结点的值,构造二叉线索树T */
/* 0(整型)/空格(字符型)表示空结点 */
TElemType h;
#if CHAR
scanf("%c",&h);
#else
scanf("%d",&h);
#endif
if(h == NIL){
*T=NULL;
}
else{
*T=(BiThrTree)malloc(sizeof(BiThrNode));
if(!*T){
exit(OVERFLOW);
}
(*T)->data = h; /* 生成根结点(先序) */
CreateBiThrTree(&(*T)->lchild); /* 递归构造左子树 */
if((*T)->lchild){ /* 有左孩子 */
(*T)->LTag = Link;
}
CreateBiThrTree(&(*T)->rchild); /* 递归构造右子树 */
if((*T)->rchild){ /* 有右孩子 */
(*T)->RTag = Link;
}
}
return OK;
}
BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
void InThreading(BiThrTree p)
{
if (p){
/* 中序遍历进行中序线索化。算法6.7 */
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); //右子树线索化
}
}
Status InOrderThreading(BiThrTree Thrt, BiThrTree T)
{//中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrTree)) )){
exit(OVERFLOW);
}
//建头结点
Thrt->LTag = Link;
Thrt->RTag = Thread;
//右指针回指
Thrt->rchild = Thrt;
//若二叉树空,则左指针回指
if (T == NULL){
Thrt->lchild = Thrt;
}
else{
Thrt->lchild = T;
pre = Thrt;
InThreading(T);
//最后一个结点线索化
pre->rchild = Thrt;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
Status InOrderTraverse_Thr(BiThrTree T,Status(*Visit)(TElemType))
{ /* 中序遍历二叉线索树T(头结点)的非递归算法。算法6.5 */
BiThrTree p;
//p指向根结点
p = T->lchild;
while(p != T){//空树或遍历结束时,p==T
while(p->LTag == Link){
p = p->lchild;
}
if(!Visit(p->data)){
return ERROR;
}
while (p->RTag == Thread && p->rchild != T){
p = p->rchild;
Visit(p->data);//访问后继结点
}
p = p->rchild;
}
return OK;
}
Status vi(TElemType c)
{
#if CHAR
printf("%c ",c);
#else
printf("%d ",c);
#endif
return OK;
}
int _tmain(int argc, _TCHAR* argv[])
{
BiThrTree H = NULL,T;
#if CHAR
printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)\n");
#else
printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n");
#endif
CreateBiThrTree(&T); /* 按先序产生二叉树 */
InOrderThreading(H,T); /* 中序遍历,并中序线索化二叉树 */
printf("中序遍历(输出)二叉线索树:\n");
InOrderTraverse_Thr(T,vi); /* 中序遍历(输出)二叉线索树 */
printf("\n");
return 0;
}