1. 程式人生 > >線索二叉樹

線索二叉樹

轉變 需要 nod ++ 當前 blog ras text pan

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

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

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

如圖6-10-3,我們把這棵二叉樹進行中序遍歷後,將所有的空指針域中的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;
}

輸出為:


技術分享圖片

由於線索二叉樹充分利用了空指針域的空間,又保證了創建時一次遍歷就可以持續受用的前驅後繼信息,所以如果所用的二叉樹需要經常遍歷或查找結點時需要某種遍歷序列中的前驅後繼,那麽采用線索二叉鏈表的存儲結構就是不錯的選擇。

線索二叉樹