线索二叉树

  咱们在上一章中,学习了二叉树的数据结构。由于二叉树的特殊性,它不一样于普通的树,因此可使用顺序存储结构来存储。可是,用顺序存储结构会存在浪费空间的弊端。以后,咱们学习了二叉链表。用链式存储结构存储树,结点结构为一个数据域data,两个指针域lchild、rchild。树的数据结构讲完了,可是没有讲怎样生成一棵二叉树。今天,咱们来学习怎样生成一棵二叉树。算法

1.生成二叉树:数据结构

  要想生成一棵二叉树,咱们须要清楚结点是否有左孩子、右孩子结点。为了搞清楚这一点,咱们须要对二叉树进行扩展,通过扩展后的二叉树,称为扩展二叉树。函数

image

咱们之前序顺序来生成上图的二叉树,前序顺序为AB#D##C##.学习

/*按前序输入二叉树中结点的值(一个字符)*/
/*#表示空树*/
CreateBirTree(BirTree T)
{
     TElemType ch; //结点值类型
     Scanf("%c",&ch); //接收输入参数
     if(ch=="#")   //判断是不是空树
        *T==Null // 树为空 
      else
{
     *T=(BirTree) malloc(sizeof(BiTNode)); //内存中开辟空间,存放结点
     if(!*T)
      exit(OVERFLOW);
      (*T)->data=ch; /*生成根结点*/
      CreateBiTree(&(*T)->lchild);  /*构造左子树*/
      CreateBiTree(&(*T)->rchild);/*构造右子树*/
}
         
}

上面就是咱们的前序建立二叉树的算法代码,咱们一样可使用后序、中序建立二叉树。算法代码,与上面大体同样,只是调换一下递归顺序。spa

 

2.线索二叉树:指针

   数据结构之因此重要,是由于好的数据结构可使计算机运做更高效。好的数据结构,应该是运行时间短、占用空间少。二叉链表中,有不少空的指针域。这些没用的指针域在内存中占有必定的空间,这是一种浪费。以下图:code

image

上图的中序遍历顺序为:HDIBJEAFCG,经过中序遍历顺序,咱们知道I的前驱是D,后继是B,F的前驱是A,后继是C。可是,这是在咱们以中序遍历整棵树以后获得的。每次,须要找某个结点的前驱、后继,咱们就须要遍历整棵树,这是时间上的浪费。blog

经过上面的空间、时间两方面的考虑,咱们须要利用那些没用的空指针域。咱们能够在建立二叉树时,利用这些空的指针域存放结点的前驱、后继。这样的话,只需一次遍历就能够知道遍历次序。以下图:递归

image  image

 

咱们利用lchild存放前驱结点、rchild存放后继结点。这样咱们就能够将二叉链表的缺点弥补。lchild、rchild指向前驱、后继结点,咱们称这样的指针为线索,加上线索的二叉树,称为线索二叉树。咱们以二叉树某种次序遍历使其变为线索二叉树的过程称作是线索化。内存

可是问题又来了,咱们怎么会知道结点的lchild、rchild是空的。因此,咱们须要增添两个记录指针域状态的变量。

image

当ltag=0、rtag=0时,指针指向左子树、右子树。

当ltag=一、rtag=1时,指针指向前驱、后继。

image

整理后的线索二叉图。

3.线索二叉树结构实现:

   线索二叉树结点结构:

/*二叉树的二叉线索存储结构定义*/
typedef enum {Link,Thread} pointerTag;/*Link==0表示指向左右孩子指针.Thread=1,表示指向前驱、后继*/

typedef struct BiThrNode /*二叉线索存储结点结构*/
{
      TElemType data; /*结点数据*/
      struct  BiThrNode  *lchild、*rchild; /*左右孩子指针*/
     pointerTag  Ltag;
     pointerTag  Rtag;  /*左右标志*/
}BiThrNode,*BiThrTree;

其实线索化的过程,就是在遍历二叉树时,将空指针指向前驱、后继的过程。

咱们看一下中序遍历线索化的过程函数代码:

BiThrTree  pre; /*全局变量,始终指向刚刚访问过的结点*/
/*中序遍历进行中序线索化*/
void InThreading(BiThrTree p)
{
     if(p)   //判断树是否为空
{
    InThreading(p->lchild); /*递归左子树线索化*/
    if!(p->lchild)                  /*判断左孩子结点是否为空*/
{
      p->Ltag=Thread;    /*指针域存放前驱*/
     p->lchild=pre;          /*将pre赋给p的前驱指针*/  
}
   if(!pre->rchild)     /*判断刚刚访问过的结点是否有右孩子*/
{
         pre->Rtag=Thread; /*指针域存放后继*/
         pre->rchild=p;
}
      pre=p;                /*将p赋给Pre*/
     InThreading(p->rchild);  /*递归右子树线索化*/
}
}

 

   pre,始终指向刚刚访问过的结点。函数首先,判断p是否为空。不为空,递归左子树。判断结点是否有左孩子,也就是判断结点的lchild指针域是否为空。当lchild指针域不为空,将标识设置为Thread,代表此结点的lchild指向前驱。将pre赋给p的lchild。由于后继元素,尚未遍历到。因此只能对Pre进行后继赋值。最后将p赋值给pre,再递归右子树线索化。咱们能够发现,中序线索化的函数,与中序遍历函数很类似。只是把输出操做,改成向指针域赋值。我没呢对线索二叉树操做,其实就是对一个双向链表操做。

咱们看一下遍历线索二叉树的函数:

/*中序遍历二叉链表*/
/*T指向头结点,头结点的右键rchild指向中序遍历的最后一个结点*/
Status InOrderTraverse_Thr(BiTree T)
{
   BiThrTree p; 
   p=T;/*p指向根结点*/
  while(p!=T)  /*判断p是否为空树、是否遍历完整棵树*/
{
     while(p->LTag==Link) /*当LTag==0时循环到中序序列的第一个结点*/
       p=p->lchild;
     printf("%c",p->data);    /*显示结点数据,能够更改成其余对结点的操做*/
        while(p->RTag==Thread && p->rchild!=T) /*读取后继结点、后继点不能为空*/
{
            p=p->rchild;
           printf("%c",p->data);
}
       p=p->rchild;
}
  return ok;
}

这就是咱们今天讲的内容了。

相关文章
相关标签/搜索