1. 程式人生 > >線索二叉樹的原理以及建立和遍歷(c++)

線索二叉樹的原理以及建立和遍歷(c++)

這是一篇非常好的關於線索二叉樹的文章,內容詳細到位,敘述清晰。作者應該是很認真、細心的人,估計花了不少時間和精力,向作者致敬!



一、線索二叉樹概念

具有 n 個結點的二叉連結串列中,其二叉連結串列的 n 個結點中共有 2n 個指標域,在這 2n 個指標域中,真正用於指向後件(左子結點或右子結點)的指標域只有 n-1 個,而另外的 n+1 個指標域都是空的。這樣就利用二叉連結串列中的空指標域,存放指向結點在某種遍歷次序下的前趨和後繼結點的指標(這種附加的指標稱為 " 線索 " ),這種加上了線索的二叉連結串列稱為線索連結串列,相應的二叉樹稱為線索二叉樹 (ThreadedBinaryTree) 。根據線索性質的不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種。

二、線索連結串列的結點結構

線索連結串列中的結點結構為: 

      ltag 和 rtag 是增加的兩個標誌域,用來區分結點的左、右指標域是指向其左、右孩子的指標,還是指向其前趨或後繼的線索。 

三、二叉樹的線索化

1 .線索化和線索化實質

將二叉樹變為線索二叉樹的過程稱為線索化 。按某種次序將二叉樹線索化的實質是:按該次序遍歷二叉樹,在遍歷過程中用線索取代空指標。

2 .二叉樹的中序線索化

( 1 )分析

演算法與中序遍歷演算法類似,只需要將遍歷演算法中訪問結點的操作具體化為建立正在訪問的結點與其非空中序前趨結點間線索。

a). 若上次訪問到的結點的右指標為空,則將當前訪問到的結點序號填入,並置右標誌域為 1 ;

b). 若當前訪問到的結點的左指標為空,則將上次訪問到的及誒單序號填入,並置左標誌域為 1

( 2 )將二叉樹按中序線索化的演算法

Cpp程式碼  
PROCEDURE INTHREAD(BT,h)  
IF BT != 0 THEN  
    {  
        INTHREAD(L(BT),h)  
        IF (h != 0) and (R(h) = 0) THEN  
            { R(h) = BT; Rflag(h) = 1; }  
        IF L(BT) = 0 THEN  
            { L(BT) = h; Lflag(BT) = 1; }  
        h = BT  
        INTHREAD(R(BT),h)  
    }  
RETURN 

  

( 3 )演算法分析

和中序遍歷演算法一樣,遞迴過程中對每結點僅做一次訪問。因此對於 n 個結點的二叉樹,演算法的時間複雜度亦為 O(n)。

3 .二叉樹的前序線索化和後序線索化

前序線索化和後序線索化演算法與二叉樹的中序線索化類似。

四、線索二叉樹的運算

1 . 在中序線索二叉樹中,查詢結點 *p 的中序後繼結點

在中序線索二叉樹中,查詢結點 *p 的中序後繼結點分兩種情形:

a). 若 *p 的右子樹空 ( 即 p->rtag 為 Thread) ,則 p->rchild 為右線索,直接指向 *p 的中序後繼。

b). 若 *p 的右子樹非空 ( 即 p->rtag 為 Link) ,則 *p 的中序後繼必是其右子樹中第一個中序遍歷到的結點。也就是從*p 的右孩子開始,沿該孩子的左鏈往下查詢,直至找到一個沒有左孩子的結點為止,該結點是 *p 的右子樹中 “ 最左下 ” 的結點,即 *P 的中序後繼結點。

具體演算法如下:

Cpp程式碼  
BinThrNode *Inorderpre(BinThrNode *p)  
{   //在中序線索樹中找結點*p的中序前趨,設p非空  
    BinThrNode *q;  
    if (p->ltag==Thread) //*p的左子樹為空  
        return p->lchild; //返回左線索所指的中序前趨  
    else{  
        q=p->lchild; //從*p的左孩子開始查詢  
        while (q->rtag==Link)  
            q=q->rchild; //右子樹非空時,沿右鏈往下查詢  
        return q; //當q的右子樹為空時,它就是最右下結點  
    } //end if  
}  


該演算法的時間複雜度不超過樹的高度 h ,即 O(h) 。

  2 . 在中序線索二叉樹中查詢結點 *p 的中序前趨結點

中序是一種對稱序,故在中序線索二叉樹中查詢結點 *p 的中序前趨結點與找中序後繼結點的方法完全對稱。具體情形如下:

a). 若 *p 的左子樹為空,則 p->1child 為左線索,直接指向 *p 的中序前趨結點;

b). 若 *p 的左子樹非空,則從 *p 的左孩子出發,沿右指標鏈往下查詢,直到找到一個沒有右孩子的結點為止。該結點是 *p 的左子樹中 “ 最右下 ” 的結點,它是 *p 的左子樹中最後一箇中序遍歷到的結點,即 *p 的中序前趨結點。

具體演算法如下:

Cpp程式碼  
BinThrNode *Inorderpre(BinThrNode *p)  
{   //在中序線索樹中找結點*p的中序前趨,設p非空  
    BinThrNode *q;  
    if (p->ltag==Thread) //*p的左子樹為空  
        return p->lchild; //返回左線索所指的中序前趨  
    else{  
        q=p->lchild; //從*p的左孩子開始查詢  
        while (q->rtag==Link)  
        q=q->rchild; //右子樹非空時,沿右鏈往下查詢  
        return q; //當q的右子樹為空時,它就是最右下結點  
    } //end if  
}   


      由上述討論可知:對於非線索二叉樹,僅從 *p 出發無法找到其中序前趨 ( 或中序後繼 ) ,而必須從根結點開始中序遍歷,才能找到 *p 的中序前趨 ( 或中序後繼 ) 。線索二叉樹中的線索使得查詢中序前趨和中序後繼變得簡單有效。

  3 . 在後序線索二叉樹中,查詢指定結點 *p 的後序前趨結點

在後序線索二叉樹中,查詢指定結點 *p 的後序前趨結點的具體規律是:

a). 若 *p 的左子樹為空,則 p->lchild 是前趨線索,指示其後序前趨結點。

b). 若 *p 的左子樹非空,則 p->lchild 不是前趨線索。由於後序遍歷時,根是在遍歷其左右子樹之後被訪問的,故 *p的後序前趨必是兩子樹中最後一個遍歷結點。

當 *p 的右子樹非空時, *p 的右孩子必是其後序前趨

當 *p 無右子樹時, *p 的後序前趨必是其左孩子

  4 . 在後序線索二叉樹中,查詢指定結點 *p 的後序後繼結點

具體的規律:

a). 若 *p 是根,則 *p 是該二叉樹後序遍歷過程中最後一個訪問到的結點。 *p 的後序後繼為空;

b). 若 *p 是其雙親的右孩子,則 *p 的後序後繼結點就是其雙親結點

c). 若 *p 是其雙親的左孩子,但 *P 無右兄弟, *p 的後序後繼結點是其雙親結點;

d). 若 *p 是其雙親的左孩子,但 *p 有右兄弟,則 *p 的後序後繼是其雙親的右子樹中第一個後序遍歷到的結點,它 是該子樹中 “ 最左下的葉結點 ” ;

由上述討論中可知:在後序線索樹中,僅從 *p 出發就能找到其後序前趨結點;要找 *p 的後序後繼 結點,僅當 *p 的右子樹為空時,才能直接由 *p 的右線索 p->rchild 得到。否則必須知道 *p 的雙親結點才能找到其後序後繼。因此,如果線索二叉樹中的結點沒有指向其雙親結點的指標,就可能要從根開始進行後序遍歷才能找到結點 *P 的後序後繼。由此,線索對查詢指定結點的後序後繼並無多大幫助。

  5 . 在前序線索二叉樹中,查詢指定結點 *p 的前序後繼結點

  6 . 在前序線索二叉樹中,查詢指定結點 *p 的前序前趨結點

在前序線索二叉樹中,找某一點 *p 的前序後繼也很簡單,僅從 *p 出發就可以找到;但找其前序前趨 也必須知道 *p的雙親結點。當樹中結點未設雙親指標時,同樣要進行從根開始的前序遍歷才能找到結點 *p 的前序前趨。

五、遍歷線索二叉樹

遍歷某種次序的線索二叉樹,只要從該次序下的開始結點開發,反覆找到結點在該次序下的後繼,直至終端結點。遍歷中序線索二叉樹演算法:

Cpp程式碼  
PROCEDURE INTHTRAV(BT)  
IF BT = 0 THEN RETURN  
h = BT  
WHILE (Lflag(h) = 0) DO h = L(h)  
OUTPUT V(h)  
WHILE (R(h) != 0 DO  
{  
    IF (Rflag(h) = 1) THEN h = R(h)  
    ELSE  
    {  
        h = R(h)  
        WHILE ((Lflag(h) = 0) and (L(h) != 0)) DO h = L(h)  
    }  
    OUTPUT V(h)  
}  
RETURN <span>  
</span>  


分析:

    a). 中序序列的終端結點的右線索為空,所以 do 語句的終止條件是 p==NULL 。

    b). 該演算法的時間複雜性為 O(n) 。因為是非遞迴演算法,常數因子上小於遞迴的遍歷演算法。因此,若對一棵二叉樹要經常遍歷,或查詢結點在指定次序下的前趨和後繼,則應採用線索連結串列作為儲存結構為宜。

    c). 以上介紹的線索二叉樹是一種全線索樹(即左右線索均要建立)。許多應用中只要建立左右線索中的一種。

    d). 若線上索連結串列中增加一個頭結點,令頭結點的左指標指向根,右指標指向其遍歷序列的開始或終端結點會更方便。

六、驗證前序線索及前序線索遍歷和中序線索及中序線索遍歷的程式

Cpp程式碼  
#include <iostream>  
#include <sstream>  
using namespace std;  
  
template<class T>  
struct tbtnode  
{  
    T s_t;  
    bool lflag;  
    bool rflag;  
    tbtnode *lchild;  
    tbtnode *rchild;  
};  
  
template<class T>  
tbtnode<T>* createtbt(tbtnode<T>* tbt, int k, ostream& out, istream& in)  
{  
    tbtnode<T> *p, *t;  
    T tmp;  
    out << "Input key: " << endl;  
    in >> tmp;  
    out << '\t' << tmp << endl;  
    if ('0' != tmp)  
    {  
        p = new tbtnode<T>;  
        p->s_t = tmp;  
        p->lchild = NULL;  
        p->rchild = NULL;  
        p->lflag = 0;  
        p->rflag = 0;  
        if (0 == k) t = p;  
        if (1 == k) tbt->lchild = p;  
        if (2 == k) tbt->rchild = p;  
  
        createtbt(p, 1, out, in);  
        createtbt(p, 2, out, in);  
    }  
  
    return (t);  
}  
  
template<class T>  
void pretraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        cout << root->s_t << " ";  
        pretraverse(root->lchild);  
        pretraverse(root->rchild);  
    }  
}  
  
template<class T>  
void intraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        intraverse(root->lchild);  
        cout << root->s_t << " ";  
        intraverse(root->rchild);  
    }  
}  
  
template<class T>  
void postraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        postraverse(root->lchild);  
        postraverse(root->rchild);  
        cout << root->s_t << " ";  
    }  
}  
  
template<class T>  
void inthread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        inthread(tbt->lchild, h);  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
        *h = tbt;  
        inthread(tbt->rchild, h);  
    }  
}  
  
template<class T>  
void prethread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
  
        *h = tbt;  
        //*h = tbt->lchild;  
        if (tbt->lflag == 0)  
            prethread(tbt->lchild, h);  
        if (tbt->rflag == 0)       
            prethread(tbt->rchild, h);  
    }  
}  
  
template<class T>  
void posthread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        posthread(tbt->lchild, h);  
        posthread(tbt->rchild, h);  
  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
        *h = tbt;  
    }  
}  
  
template<class T>  
void inthtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
    while (h->lflag == 0) h = h->lchild;  
    cout << h->s_t << " ";  
    while (h->rchild != NULL)  
    {  
        if (h->rflag == 1)  
            h = h->rchild;  
        else  
        {  
            h = h->rchild;  
            while ((h->lflag == 0) && (h->lchild != NULL))  
                h = h->lchild;  
        }  
        cout << h->s_t << " ";  
    }  
}  
  
template<class T>  
void prethtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
    cout << tbt->s_t << " ";  
    while (h->rchild != NULL)  
    {  
        if ((h->lflag == 0) && (h->lchild != NULL))  
        {  
            h = h->lchild;  
        }  
        else  
        {  
            h = h->rchild;  
        }  
        cout << h->s_t << " ";  
    }  
}  
  
template<class T>  
void posthtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
}  
  
int main()  
{  
    typedef tbtnode<char> tbtnode_char;  
    tbtnode_char *root;  
    //istringstream ssin(string("AB0DG000CE0H00F00"));  
    istringstream ssin(string("ABCD00E00FG00H00IJK00L00MN00O00"));  
    root = createtbt(root, 0, cout, ssin);  
    pretraverse(root);  
    cout << endl;  
    intraverse(root);  
    cout << endl;  
    postraverse(root);  
    cout << endl;  
  
    cout << "通過穿線二叉樹進行遍歷" << endl;  
    tbtnode_char *h = NULL;  
  
    //inthread(root, &h);  
    //inthtraverse(root);  
  
    prethread(root, &h);  
    prethtraverse(root);  
  
    cout << endl;  
  
    return 0;  
}