【筆記】線索二叉樹

Time-space發表於2017-10-29

  採用二叉連結串列作為二叉樹的儲存結構,只能找到結點的左、右孩子結點,而不能直接找到該結點的直接前驅和後繼結點資訊,這種資訊只能在對二叉樹的遍歷過程中才能找到,顯然這不是最直接、最簡便的方法。為了能快速找到任何一個結點的直接前驅和後繼結點資訊,需要對二叉樹進行線索化。

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步:

  1. 從根結點出發,找到二叉樹的最左下端結點並訪問;
  2. 判斷該結點的右標誌域是否為線索指標,若為線索指標即p->rtag=Thread,表明p->rchild指向的是後繼節點,則將指標移動到右孩子結點;
  3. 將當前指標指向該右孩子結點。

  重複以上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);         /*右子樹線索化*/
    }
} 
  • 測試結果


這裡寫圖片描述

  
  

  
  
  
  

相關文章