資料結構:線索二叉樹(Threaded Binary Tree)

來源:互聯網
上載者:User

我們知道滿二叉樹只是一種特殊的二叉樹,大部分二叉樹的結點都是不完全存在左右孩子的,即很多指標域沒有被充分地利用。另一方面我們在對一棵二叉樹做某種次序遍曆的時候,得到一串字元序列,遍曆過後,我們可以知道結點之間的前驅後繼關係,也就是說,我們可以很清楚地知道任意一個結點,它的前驅和後繼是哪一個。可是這是建立在已經遍曆過的基礎之上的。在二叉鏈表上,我們只能知道每個結點指向其左右孩子結點的地址,而不知道某個結點的前驅是誰,後繼是誰。要想知道,必須遍曆一次。以後每次需要知道時,都必須遍曆一次。為什麼不考慮在建立時就記住這些前驅和後繼呢?那將是多大的時間上的節省。

綜合剛才兩個角度的分析後,我們可以考慮利用那些空地址,存放指向結點在某次遍曆次序下的前驅和後繼結點的地址。我們把這種指向前驅和後繼的指標稱為線索,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹就稱為線索二叉樹(Threaded Binary Tree)。

6-10-2,我們把這棵二叉樹進行中序遍曆後,將所有的null 指標域中的rchild,改為指向它的後繼結點。

6-10-3,我們把這棵二叉樹進行中序遍曆後,將所有的null 指標域中的lchild,改為指向它的前驅結點。



通過圖6-10-4(空心箭頭實線為前驅,虛線黑箭頭為後繼),更容易看出,其實線索二叉樹,等於是把一棵二叉樹轉變成了一個雙向鏈表,這樣對我們的插入刪除結點,尋找某個結點都帶來了方便。所以我們把對二叉樹以某種次序遍曆使其變為線索二叉樹的過程稱做是線索化。



為了區分指標域是指向左右孩子還是前驅後繼,需要再增加兩個標誌位ltag和rtag,ltag為0時表示指向左孩子,為1時表示指向前驅,rtag為0時表示指向右孩子,為1時表示指向後繼。和雙向鏈表結構一樣,可以在二叉樹線索鏈表上添加一個頭結點,6-10-6所示,這樣做的好處是我們既可以從第一個結點H開始順後繼進行遍曆(利用1,4兩根線),也可以從最後一個結點G開始順前驅進行遍曆(利用2,3兩根線),將頭結點作為遍曆結束的判據。


樣本程式如下:(改編自《大話資料結構》)

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include<iostream>
using namespace std;

#define MAXSIZE 50

typedef char ElemType;
typedef enum { Link, Thread } PointerTag;
typedef char String[MAXSIZE + 1]; //以'\0’結尾
String str; /* 用於構造二叉樹*/

/* 結點結構 */
typedef struct BThrNode
{
    ElemType data;/* 結點資料 */
    struct BThrNode *LChild;/* 左右孩子指標 */
    struct BThrNode *RChild;
    PointerTag LTag;
    PointerTag RTag;

} BThrNode, *BThrNodePtr;

/* 構造一個字串 */
bool StrAssign(String Dest, char *ptr)
{
    cout << "Assign Str ..." << endl;
    int i;
    for (i = 0; ptr[i] != '\0' && i < MAXSIZE; i++)
        Dest[i] = ptr[i];
    Dest[i] = '\0';
    return true;
}

bool  CreateBThrTree(BThrNodePtr *Tpp)
{
    ElemType ch;
    static int i = 0;
    if (str[i] != '\0')
        ch = str[i++];
    if (ch == '#')
        *Tpp = NULL;
    else
    {
        *Tpp = (BThrNodePtr)malloc(sizeof(BThrNode));
        if (!*Tpp)
            exit(1);
        (*Tpp)->data = ch;/* 產生根結點 */
        CreateBThrTree(&(*Tpp)->LChild);/* 構造左子樹 */
        if ((*Tpp)->LChild)
            (*Tpp)->LTag = Link;
        CreateBThrTree(&(*Tpp)->RChild);/* 構造右子樹 */
        if ((*Tpp)->RChild)
            (*Tpp)->RTag = Link;

    }
    return true;

}

BThrNodePtr prev;/* 全域變數,始終指向剛剛訪問過的結點 */
/* 中序遍曆進行中序線索化 */
void InThreading(BThrNodePtr Tp)
{
    if (Tp)
    {
        InThreading(Tp->LChild);/* 在第一次左遞迴過程中綁定了的線條3 */
        if (!Tp->LChild)/* 沒有左孩子 */
        {
            Tp->LTag = Thread;/* 前驅線索 */
            Tp->LChild = prev;/* 左孩子指標指向前驅 */
        }
        if (!prev->RChild)/* 前驅沒有右孩子 */
        {
            prev->RTag = Thread;/* 後繼線索 */
            prev->RChild = Tp;/* 前驅右孩子指標指向後繼(當前結點Tp) */
        }

        prev = Tp;
        InThreading(Tp->RChild);/* 遞迴右子樹線索化 */
    }
}
/* 中序遍曆二叉樹,並將其中序線索化,*Hpp指向頭結點 */
bool InOrderThreading(BThrNodePtr *Hpp, BThrNodePtr Tp)
{
    cout << "InOrderThreading ..." << endl;
    *Hpp = (BThrNodePtr)malloc(sizeof(BThrNode));
    if (!(*Hpp))
        exit(1);
    (*Hpp)->LTag = Link;/* 建頭結點 */
    (*Hpp)->RTag = Thread;
    (*Hpp)->RChild = (*Hpp);/* 右指標回指 */
    if (!Tp)
        (*Hpp)->LChild = *Hpp;/* 若二叉樹空,則左指標回指 */
    else
    {
        (*Hpp)->LChild = Tp; /* 綁定的線1 */
        prev = (*Hpp); /* 頭結點是第一個走過的點*/
        InThreading(Tp); /* 中序遍曆進行中序線索化 */
        prev->RChild = *Hpp; /* 最後一個結點的後繼指向頭結點,即的線4*/
        prev->RTag = Thread;
        (*Hpp)->RChild = prev; /* 頭結點的後繼指向最後一個結點,即的線2*/
    }
}
/* 中序遍曆二叉線索樹(頭結點)的非遞迴演算法 */
bool InOrderTraverse_Thr(BThrNodePtr Hp)
{
    cout << "InOrderTraverse ..." << endl;
    BThrNodePtr Bp;
    Bp = Hp->LChild;/* Bp指向根結點 */
    while (Bp != Hp)
    {
        /* 空樹或遍曆結束時,Bp== Hp */
        while (Bp->LTag == Link)
            Bp = Bp->LChild;
        /* 訪問其左子樹為空白的結點 */
        cout << Bp->data << ' ';

        while (Bp->RTag == Thread && Bp->RChild != Hp)
        {
            Bp = Bp->RChild;
            cout << Bp->data << ' '; /* 訪問後繼結點 */
        }

        Bp = Bp->RChild;
    }

    return true;
}

int main(void)
{
    BThrNodePtr Hp, Tp;
    StrAssign(str, "ABDH##I##EJ###CF##G##");
    cout << "輸入前序走訪序列 :" << endl;
    cout << str << endl;
    CreateBThrTree(&Tp);
    InOrderThreading(&Hp, Tp);
    InOrderTraverse_Thr(Hp);

    return 0;
}

輸出為:



由於線索二叉樹充分利用了null 指標域的空間,又保證了建立時一次遍曆就可以持續受用的前驅後繼資訊,所以如果所用的二叉樹需要經常遍曆或尋找結點時需要某種遍曆序列中的前驅後繼,那麼採用線索二叉鏈表的儲存結構就是不錯的選擇。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.