1. 程式人生 > >二叉樹的線索化(摘自天勤資料結構高分筆記)

二叉樹的線索化(摘自天勤資料結構高分筆記)

/************************************************************************/ /*      二叉樹的線索化:二叉樹的線索化可以將二叉樹非遞迴遍歷演算法中的使用者棧也給省掉,進一步提高效率     中序線索二叉樹的結點結構如下:          ______________________________________         | lchild | Ltag | data | Rtag | Rchild |       ---------------------------------------       在二叉樹線索化的過程當中會把二叉樹中的空指標利用起來作為尋找點錢結點前驅和後繼的線索,這樣就出現了一個問題,即線索和樹中原有指向孩子結點的指標就無法       區分。上述的結點設計就是為了區分這兩類指標,其中Ltag和Rtag為標識域,其含義如下:         1、若Ltag==0,則表示lchild為指標,指向結點的左孩子;若Ltag==1,則表示lchild為線索,指向結點的直接前驅         2、若Rtag==0,則表示rchild為指標,指向結點的有孩子;若Rtag==1,則表示rchild為線索,指向結點的直接後繼         對應的二叉樹的結點定義如下: */ /************************************************************************/

typedef struct TBTNode
{
    char data;
    int Ltag, Rtag;    //線索標記
    struct TBTNode *lchild;
    struct TBTNode *rchild;
}TBTNode;

/************************************************************************/ /*  線索二叉樹可以分為前序線索二叉樹、後序線索二叉樹、中序線索二叉樹。本處一後續線索二叉樹為例 二叉樹中序線索化思路分析:     1、既然要對二叉樹進行中序線索化,首先要有個中序遍歷的框架。這裡採用二叉樹遞迴遍歷演算法,在遍歷過程中連線上合適的線索即可     2、線索化的規則是:左線索指標指向當前結點在中序遍歷序列中的前驅結點,右線索指標指向後繼結點。因此我們需要一個指標P指向     當前正在訪問的結點,pre指向P的前驅結點,P的左線索如果存在則讓其指向pre,pre的右線索如果存在則讓其指向P,因為P是pre的後繼     結點,這樣就完成了一對線索的連線。按照這樣的規則一直進行下去,當整棵二叉樹遍歷完成的時候,線索化也就完成了。(天勤資料結構     高分筆記P151)     3、上一步中保持pre始終指向p的前驅的具體過程是:當P要離開一個訪問過的結點時,pre指向P;當P來到一個新結點時,pre顯然指向的     是此時p所指向結點的前驅結點     通過中序遍歷對二叉樹進行線索化的遞迴演算法如下: */ /************************************************************************/

void InThread(TBTNode *P, TBTNode *&pre)
{
    if (P != NULL)
    {
        InThread(P->lchild, pre);        //遞迴,左子樹進行線索化
        if (P->lchild == NULL)
        {
            //建立當前結點的前驅線索
            P->lchild = pre;
            P->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL )
        {
            //建立前驅結點的後繼線索
            pre->rchild = P;
            pre->Rtag = 1;
        }
        pre = P;                         //pre指向當前的P,作為P將要指向的下一個結點的前驅結點指示指標
        P = P->rchild;                    //P指向一個新結點,此時pre和P分別指向的結點想成了一個前驅後繼對,為下一次線索的連線做準備
        InThread(pre->rchild, pre);       //遞迴,右子樹線索化
    }
}
/************************************************************************/
/* 
    if (P->lchild == NULL)
        {
            //建立當前結點的前驅線索
            P->lchild = pre;
            P->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL )
        {
            //建立前驅結點的後繼線索
            pre->rchild = P;
            pre->Rtag = 1;
        }
        pre = P;                         //pre指向當前的P,作為P將要指向的下一個結點的前驅結點指示指標
        P = P->rchild;                    //P指向一個新結點,此時pre和P分別指向的結點想成了一個前驅後繼對,為下一次線索的連線做準備
這一段程式碼就相當於二叉樹遍歷演算法模板中的visit()函式
*/

/************************************************************************/ //通過中序建立中序線索二叉樹的主程式如下:

void CreateInThread(TBTNode *root)
{
    TBTNode *pre = NULL;        //前驅結點指標
    if (NULL != root)
    {
        InThread(root, pre);
        pre->rchild = NULL;     //非空二叉樹線索化
        pre->Rtag = 1;        //後處理中序最後一個結點

    }
}

//遍歷中序線索化二叉樹:訪問運算主要是為遍歷中序線索二叉樹服務的,這種遍歷不再需要棧,因為它利用了隱含線上索二叉樹中的前驅和後繼資訊 //求以P為根結點的中序線索二叉樹中,中序序列下的第一個結點的演算法如下:

TBTNode *First(TBTNode *P)
{
    while (P->Ltag == 0)
        P = P->lchild;        //最坐下結點不一定是葉節點(可能該結點只有右孩子沒有左孩子)
    return P;
}
//求在中序線索二叉樹中,結點P在中序下的後繼結點的演算法如下:
TBTNode *Next(TBTNode *p)
{
    if (0 == p->Rtag)
        return First(p->lchild);
    else
        return p->rchild;    // Rtag == 1,直接返回後繼線索
}

/************************************************************************/ /*      如果把函式First中的Ltag和lchild分別換成Rtag和rchild,同時把函式名換成Last,則可以得到求中序序列下最後一個結點的函式Last();     如果把函式Next中的Rtag和rchild換成Ltag和lchild,並同時把函式First()換成Last(),再把函式名Next改成Prior則可以求得中序序列下前驅結點的函式Perior() */ /************************************************************************/ //最後很容易的就可以寫出在中序線索二叉樹上執行中序遍歷的演算法

void Visit(TBTNode *p)
{

}
void Inorder(TBTNode *root)
{
    for (TBTNode *p = First(root); p != NULL; p = Next(p))
        Visit(p);
}

//前序線索二叉樹:前序線索化程式碼和中序線索化程式碼極為相似,最大的區別就是把連線線索的程式碼提到了兩個遞迴入口的前面,這還是符合先序遞迴遍歷的框架

void PreThread(TBTNode *p, TBTNode *&pre)
{
    if (p != NULL)
    {
        if (p->lchild == NULL)
        {
            p->lchild = pre;
            p->Ltag = 1;
        }
        if (pre != NULL && pre->rchild == NULL)
        {
            pre->lchild = p;
            pre->Rtag = 1;
        }
        pre = p;
        //注意在這裡遞迴入口處有限制條件,當且僅當做指標不是線索時才繼續遞迴
        if (p->Ltag == 0)
            PreThread(p->lchild, pre);
        if (p->Rtag == 0)
            PreThread(p->rchild, pre);
    }
}

//在前序線索二叉樹上執行前序遍歷的演算法如下:

void Preorder(TBTNode *root)
{
    if (NULL != root)
    {
        TBTNode *p = root;
        while (p != NULL)
        {
            while (p->Ltag == 0)    //左指標不是線索,則變訪問邊左移
            {
                Visit(p);
                p = p->lchild;
            }
            Visit(p);        //此時左指標必為線索,但還沒有被訪問,則訪問
            p = p->rchild;    //此時p的左孩子不存在,則右指標若非空,則不論是否為線索都指向其後繼
        }
    }
}

//後序線索二叉樹:後續線索化程式碼和中序線索化程式碼即為相似,最大的區別就在於把連線線索的程式碼放到了兩個遞迴入口的後邊。這也符合後序遍歷的框架

void PostThread(TBTNode *p, TBTNode *&pre)
{
    if (p != NULL)
    {
        PostThread(p, pre);        //遞迴,左子樹線索化
        PostThread(p, pre);        //遞迴,右子樹線索化
        if (NULL == p->lchild)
        {
            p->lchild = pre;
            p->Ltag = 1;
        }
        if (pre != NULL && pre->lchild == NULL)
        {
            pre->rchild = p;
            pre->Rtag = 1;
        }
        pre = p;
    }
}