16 - 12 - 24 二叉树的中序线索化

线索二叉树
按照某种遍历方式对二叉树进行遍历时,可以把二叉树中的所有节点排列成为一个线性序列,在该序列中,除了第一个节点外,每个节点有且仅有一个直接前驱(直接后继), 但是 ! 当以二叉链表作为存储结构时,只能得到节点的左右孩子信息,而不能直接得到节点在某种遍历序列中的前驱和后继节点。这种信息只有在对二叉树进行动态遍历的过程中才能得到。
为了保存节点在某种遍历序列中直接前驱(后继)信息,可以利用二叉树的空指针域来存放。一个具有n个节点的二叉树(二叉链表),
在 2n 个指针域中只有 n-1 个指针域用来存放节点孩子的地址,另外n+1个指针域都为空。这些空的指针域就可以用来存放某种前驱后继。这种指向前驱后继的指针成为线索,加了线索的二叉树
叫做线索二叉树。以某种次序遍历使其变成线索二叉树的过程称为
线索化。ltag和rtag只是区分0或1数字的布尔型变量(用枚举常量定义)
- 一个节点的五个域:
lchild ltag data rtag rchild
– ltag 为0时指向该节点的左孩子,为1时指向该节点的前驱
–rtag 为0时指向该节点的右孩子,为1时指向该节点的后继
–要设置一个全局变量 pre 始终指向刚刚访问过的节点,即:若:指针p指向当前正在访问的节点,则pre 指向p的前驱,p指向pre的后继。这样便于增设线索。
要点:
1、为了方便,我们对二叉树线索化的时候要增加一个头节点。
2、头节点的lchild 指向二叉树 根节点,
其rchild 指向指向二叉树中序遍历时最后一个结点。
3、中序二叉树第一个结点的线索指针指向头节点,
4、初始化时,头节点的lchild、rchild 都指向头节点(自己),并将头节点的 ltag 置为Link :0,rtag置为Thread:1

–在访问p所指的节点时,所做的处理如下:
1):建立p的前驱检索 :
若p->lchild为空,则将其左标志域置为1,并令p->lchild指向其中序前驱pre
2):建立pre的后继检索
若pre->rchild为空,则将其右标志域置1,并令pre->rchild
指向其中序后继p
3):将pre指向p刚刚访问过的节点, 即 pre = p;这样,在p访问一个新节点时,pre为其前驱结点。


// 中序遍历线索化二叉树:
/* thread  n. 线索,蛛丝马迹*/
typedef char ElemType ;  //千万千万别忘了“;”啊!!!
typedef enum{Link, Thread} PointerTag ;  注意分号!!
typedef struct BiNode {
    ElemType data;
    struct BiNode *lchild,*rchild;
    PointerTag ltag,rtag;
}*BiTree , BiNode ;
BiTree pre;  //全局变量
/* BiTree p;和 BiNode * p是等价的(确定)*/
//**pre 是我们增加定义的头节点:在上一个函数里已经定义好了。
void In_Thread(BiTree p) {
    if ( p ) {
        In_Thread( p->lchild);   //不断向左下查找

if ( !p->lchild ) {      //没有左子树                p->ltag = Thread ;   //=1.,加个线索    
            p->lchild = pre ;    //看:要点2!
        }
        if ( !pre->rchild) {    //没有右子树
            pre->rtag = Thread;
            pre->rchild = p;
         }

        pre = p;
        In_Thread(p->rchild);   //右子树线索化
     }
} 
/*整个脉络很清晰,复习数据结构也能很容易的看懂了,整个函数 严格按照In_Thread( p->lchild);  
         .......  pre = p;
         In_Thread(p->rchild);   
*/
/*1、不断向左查找,直到程序中的if(p)即现实中的if(p->lchild)不成立(此时的p指向最底层、最左边的那个叶节点),结束这层递归。程序继续向下执行if(!p->lchild) 语句(程序中加粗部分)。
给这个最左最下的叶节点的ltag 赋值1,pre指向刚才访问过的节点,(p->lchild指向其中序前驱pre)。 程序继续向下执行if(!p->rchild) 语句,给rtag赋值为1,将p的地址赋给pre->rchild
中间部分代码做了这样的事情:
因为此时p结点的后继还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断,if(!pre->rchild)表示如果为空,则p就是pre的后继,于是pre->rchild = p,并且设置pre->rtag = Thread,完成后继结点的线索化。
T->lchild = pre 的理解:
因为是中序遍历,而pre是前驱,也就是通过pre能访问到 现在所访问的节点的前一个(中序)。当T是叶节点时,T->lchild是NULL,所以要把pre的地址赋给T->child.

但是。在执行左边的函数表达式时,编译通过,但发生了未知错误。为啥呢?? 在pre = T(第一次真正的赋值)之前,就已经要调用pre->rchild了( p->rchild为空),虽然在前面BiTree pre;但pre并没有一个具体的所指,所以我们需要一个头指针。*/









/*中序遍历线索化*/  /*这一段是重复的,可略过*/
/*不过要注意,In_Thread函数要写在InOrderThread前面*/
void In_Thread(BiTree p) {
    if ( p ) {
        In_Thread( p->lchild);   //1、   //左子树线索化

if ( !p->lchild ) {        //没有左孩子                  p->ltag = Thread ;   //=1.,加个线索    
            p->lchild = pre ;    
        }
        if ( !pre->rchild) {    //没有右孩子
            pre->rtag = Thread;
            pre->rchild = p;
         }
        pre = p;
        In_Thread(p->rchild);   //右子树线索化
     }
int InOrderThreading(BiTree *Thrt,BiTree T)
{   /*Thrt是指向头节点的指针 */
    *Thrt = (BiTree)malloc(sizeof(BiNode));  
     if(!(*Thrt))  exit (-1);
 /*线索化头节点*/
     (*Thrt)->ltag = Link   ;   (*Thrt)->rtag = Thread ; 
 (*Thrt)->rchild = *Thrt ;  
     if(T==NULL)  
        (*Thrt)->rchild = *Thrt ;
     else{ 
       (*Thrt)->lchild = T;//将头节点的左孩子指针指向根节点        
pre = *Thrt;  /*重要:将pre指向线索化的头节点*/
In_Thread(T); /*调用函数中序遍历线索化*/ 
       /*将最后一个节点线索化*/ 
         pre->rtag = Thread;   /*修改标志域*/
         pre->rchild = *Thrt;  /*最后一个节点的后继指向头指针*/ 
         (*Thrt) ->rchild = pre;  /*将pre节点设为头节点的右孩子*/
       } 
       return 1; 
} 
/**Thrt所指的头节点 的左孩子时根节点,右孩子指向自己,
ltag = 0 表示有左孩子,rtag = 1表示没有右孩子。
当二叉树为空时,把左孩子也指向自己;不为空时左孩子是头节点。
并令 pre 指向 *Thrt   这样pre就有所指啦~~~
为什么 *Thrt一定要把右孩子指向自己呢?因为上文提到,在
pre = T 之前,我们就调用了 pre->rchild, 因为pre没有所指,所以程序出现地址保护 从而不允许我们访问。
这里将*Thrt(也就是一开始pre所指向的)的右孩子指向自己,
从而保证整个程序的顺利运行。
总结程序,相当于时一个链表的扫描,所以时间复杂度是O(n)
当需要经常 遍历查找 某种 遍历序列 的前驱后继时,十分方便。
*/
/*下面是应用:

【例一】:
查找某节点(*p)的直接前驱/后继。*/

BiTree SearchPreOrder(BiNode *p)    //  <-【前驱】
{ 
    BiNode *ptr;
    if(p->ltag == Thread)  
       return p->lchild;         /*如果p存在标志域,说明他没有左节点,lchild里面存放的是p的前驱*/ 

    else{                  //1、
      ptr = p->lchild;      //2、   
       while(ptr->rtag == Link)    //3、
        ptr = ptr->rchild;    /*向下寻找*/
       return ptr;     /*pre就是最右下端节点*/                  
} 
//1、  说明有左孩子 ,如果有左孩子,那就向下查找p的左子树的最右下端的节点 
//2、  将左孩子指针赋给ptr,寻找左子树的最右下端节点
//3、  使劲向下寻找右孩子,直到 pre->rtag ==1说明没有右           孩子了 
BiTree SearchPostOrder ( BiNode *p)   //<-【后继】  
{   
    if(p->rtag == Thread)    //没有右孩子 
        return p->rchild;    //rchild存放的是p的后继 
    else     
    {           //寻找右子树的最左下节点 
        p = p->rchild;
        while(p->ltag == 0)
          p = p->lchild;
        return p; 
    } 
}
/*线索二叉树的遍历*/
/*在线索树上进行遍历,只要先找到根节点,不断地向左向下找叶子,之后以叶子作为SearchPostOrder 的参数,不断地寻找后继即可。*/
//遍历线索树
void InOrderDispaly(BiTree T)
{
    BiTree ptr = T ; 
    if ( ptr )
    {
        while(ptr->ltag==0)
        ptr = ptr->lchild;  //直到寻找到最左下节点 
    }
    do{
        printf("%3c",ptr->data);
        ptr =  SearchPostOrder(ptr);
 }while(ptr);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值