一、線索二叉樹的原理
通過考察各種二叉鏈表,不管兒叉樹的形態如何,空鏈域的個數總是多過非空鏈域的個數。准確的說,n各結點的二叉鏈表共有2n個鏈域,非空鏈域為n-1個,但其中的空鏈域卻有n+1個。如下圖所示。
因此,提出了一種方法,利用原來的空鏈域存放指針,指向樹中其他結點。這種指針稱為線索。
記ptr指向二叉鏈表中的一個結點,以下是建立線索的規則:
(1)如果ptr->lchild為空,則存放指向中序遍歷序列中該結點的前驅結點。這個結點稱為ptr的中序前驅;
(2)如果ptr->rchild為空,則存放指向中序遍歷序列中該結點的后繼結點。這個結點稱為ptr的中序后繼;
顯然,在決定lchild是指向左孩子還是前驅,rchild是指向右孩子還是后繼,需要一個區分標志的。因此,我們在每個結點再增設兩個標志域ltag和rtag,注意ltag和rtag只是區分0或1數字的布爾型變量,其占用內存空間要小於像lchild和rchild的指針變量。結點結構如下所示。
其中:
(1)ltag為0時指向該結點的左孩子,為1時指向該結點的前驅;
(2)rtag為0時指向該結點的右孩子,為1時指向該結點的后繼;
(3)因此對於上圖的二叉鏈表圖可以修改為下圖的養子。
二、線索二叉樹結構實現
二叉線索樹存儲結構定義如下:
-
/* 二叉樹的二叉線索存儲結構定義*/
-
typedef
enum{Link, Thread}PointerTag;
//Link = 0表示指向左右孩子指針;Thread = 1表示指向前驅或后繼的線索
-
-
typedef
struct BitNode
-
{
-
char data;
//結點數據
-
struct BitNode *lchild, *rchild;
//左右孩子指針
-
PointerTag Ltag;
//左右標志
-
PointerTag rtal;
-
}BitNode, *BiTree;
線索化的實質就是將二叉鏈表中的空指針改為指向前驅或后繼的線索。由於前驅和后繼信息只有在遍歷該二叉樹時才能得到,所以,線索化的過程就是在遍歷的過程中修改空指針的過程。
中序遍歷線索化的遞歸函數代碼如下:
-
BiTree pre;
//全局變量,始終指向剛剛訪問過的結點
-
//中序遍歷進行中序線索化
-
void InThreading(BiTree p)
-
{
-
if(p)
-
{
-
InThreading(p->lchild);
//遞歸左子樹線索化
-
//===
-
if(!p->lchild)
//沒有左孩子
-
{
-
p->ltag = Thread;
//前驅線索
-
p->lchild = pre;
//左孩子指針指向前驅
-
}
-
if(!pre->rchild)
//沒有右孩子
-
{
-
pre->rtag = Thread;
//后繼線索
-
pre->rchild = p;
//前驅右孩子指針指向后繼(當前結點p)
-
}
-
pre = p;
-
//===
-
InThreading(p->rchild);
//遞歸右子樹線索化
-
}
-
}
上述代碼除了//===之間的代碼以外,和二叉樹中序遍歷的遞歸代碼機會完全一樣。只不過將打印結點的功能改成了線索化的功能。
中間部分代碼做了這樣的事情:
因為此時p結點的后繼還沒有訪問到,因此只能對它的前驅結點pre的右指針rchild做判斷,if(!pre->rchild)表示如果為空,則p就是pre的后繼,於是pre->rchild = p,並且設置pre->rtag = Thread,完成后繼結點的線索化。如圖:
if(!p->lchild)表示如果某結點的左指針域為空,因為其前驅結點剛剛訪問過,賦值了pre,所以可以將pre賦值給p->lchild,並修改p->ltag = Thread(也就是定義為1)以完成前驅結點的線索化。
完成前驅和后繼的判斷后,不要忘記當前結點p賦值給pre,以便於下一次使用。
有了線索二叉樹后,對它進行遍歷時,其實就等於操作一個雙向鏈表結構。
和雙向鏈表結點一樣,在二叉樹鏈表上添加一個頭結點,如下圖所示,並令其lchild域的指針指向二叉樹的根結點(圖中第一步),其rchild域的指針指向中序遍歷訪問時的最后一個結點(圖中第二步)。反之,令二叉樹的中序序列中第一個結點中,lchild域指針和最后一個結點的rchild域指針均指向頭結點(圖中第三和第四步)。這樣的好處是:我們既可以從第一個結點起順后繼進行遍歷,也可以從最后一個結點起順前驅進行遍歷。
遍歷代碼如下所示。
-
//t指向頭結點,頭結點左鏈lchild指向根結點,頭結點右鏈rchild指向中序遍歷的最后一個結點。
-
//中序遍歷二叉線索樹表示二叉樹t
-
int InOrderThraverse_Thr(BiTree t)
-
{
-
BiTree p;
-
p = t->lchild;
//p指向根結點
-
while(p != t)
//空樹或遍歷結束時p == t
-
{
-
while(p->ltag == Link)
//當ltag = 0時循環到中序序列的第一個結點
-
{
-
p = p->lchild;
-
}
-
printf(
"%c ", p->data);
//顯示結點數據,可以更改為其他對結點的操作
-
while(p->rtag == Thread && p->rchild != t)
-
{
-
p = p->rchild;
-
printf(
"%c ", p->data);
-
}
-
-
p = p->rchild;
//p進入其右子樹
-
}
-
-
return OK;
-
}
說明:
(1)代碼中,p = t->lchild;意思就是上圖中的第一步,讓p指向根結點開始遍歷;
(2)while(p != t)其實意思就是循環直到圖中的第四步出現,此時意味着p指向了頭結點,於是與t相等(t是指向頭結點的指針),結束循環,否則一直循環下去進行遍歷操作;
(3)while(p-ltag == Link)這個循環,就是由A->B->D->H,此時H結點的ltag不是link(就是不等於0),所以結束此循環;
(4)然后就是打印H;
(5)while(p->rtag == Thread && p->rchild != t),由於結點H的rtag = Thread(就是等於1),且不是指向頭結點。因此打印H的后繼D,之后因為D的rtag是Link,因此退出循環;
(6)p=p->rchild;意味着p指向了結點D的右孩子I;
(7).....,就這樣不斷的循環遍歷,直到打印出HDIBJEAFCG,結束遍歷操作。
從這段代碼可以看出,它等於是一個鏈表的掃描,所以時間復雜度為O(n)。
由於充分利用了空指針域的空間(等於節省了空間),又保證了創建時的一次遍歷就可以終生受用后繼的信息(意味着節省了時間)。所以在實際問題中,如果所用的二叉樹需要經過遍歷或查找結點時需要某種遍歷序列中的前驅和后繼,那么采用線索二叉鏈表的存儲結構就是非常不錯的選擇。
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
#define ERROR 0
-
#define OK 1
-
-
typedef
enum{Link, Thread} PointerTag;
//link = 0表示指向左右孩子指針
-
//Thread = 1表示指向前驅或后繼的線索
-
typedef
struct BitNode
-
{
-
char data;
//結點數據
-
struct BitNode *lchild;
//左右孩子指針
-
struct BitNode *rchild;
-
PointerTag ltag;
//左右標志
-
PointerTag rtag;
-
}BitNode, *BiTree;
-
-
BiTree pre;
//全局變量,始終指向剛剛訪問過的結點
-
-
//前序創建二叉樹
-
void CreateTree(BiTree *t)
-
{
-
char ch;
-
scanf(
"%c", &ch);
-
-
if(ch ==
'#')
-
{
-
*t =
NULL;
-
}
-
else
-
{
-
(*t) = (BiTree)
malloc(
sizeof(BitNode));
-
if((*t) ==
NULL)
-
{
-
return;
-
}
-
(*t)->data = ch;
-
CreateTree(&((*t)->lchild));
-
CreateTree(&((*t)->rchild));
-
}
-
}
-
-
-
//t指向頭結點,頭結點左鏈lchild指向根結點,頭結點右鏈rchild指向中序遍歷的最后一個結點。
-
//中序遍歷二叉線索樹表示的二叉樹t
-
int InOrderThraverse_Thr(BiTree t)
-
{
-
BiTree p;
-
p = t->lchild;
//p指向根結點
-
while(p != t)
-
{
-
while(p->ltag == Link)
//當ltag = 0時循環到中序序列的第一個結點
-
{
-
p = p->lchild;
-
}
-
printf(
"%c ", p->data);
//顯示結點數據,可以更改為其他對結點的操作
-
while(p->rtag == Thread && p->rchild != t)
-
{
-
p = p->rchild;
-
printf(
"%c ", p->data);
-
}
-
-
p = p->rchild;
//p進入其右子樹
-
}
-
-
return OK;
-
}
-
-
//中序遍歷進行中序線索化
-
void InThreading(BiTree p)
-
{
-
if(p)
-
{
-
InThreading(p->lchild);
//遞歸左子樹線索化
-
if(!p->lchild)
//沒有左孩子
-
{
-
p->ltag = Thread;
//前驅線索
-
p->lchild = pre;
//左孩子指針指向前驅,這里是第3步
-
}
-
if(!pre->rchild)
//沒有右孩子
-
{
-
pre->rtag = Thread;
//后繼線索
-
pre->rchild = p;
//前驅右孩子指針指向后繼(當前結點p)
-
}
-
pre = p;
-
-
InThreading(p->rchild);
//遞歸右子樹線索化
-
}
-
}
-
//建立頭結點,中序線索二叉樹
-
int InOrderThread_Head(BiTree *h, BiTree t)
-
{
-
(*h) = (BiTree)
malloc(
sizeof(BitNode));
-
if((*h) ==
NULL)
-
{
-
return ERROR;
-
}
-
-
(*h)->rchild = *h;
-
(*h)->rtag = Link;
-
-
if(!t)
//如果為NULL
-
{
-
(*h)->lchild = *h;
-
(*h)->ltag = Link;
-
}
-
else
-
{
-
pre = *h;
-
(*h)->lchild = t;
//第一步
-
(*h)->ltag = Link;
-
InThreading(t);
//找到最后一個結點
-
pre->rchild = *h;
//第四步
-
pre->rtag = Thread;
-
(*h)->rchild = pre;
//第二步
-
}
-
}
-
-
int main(int argc, char **argv)
-
{
-
BiTree t;
-
BiTree temp;
-
-
printf(
"請輸入前序二叉樹的內容:\n");
-
CreateTree(&t);
//建立二叉樹
-
InOrderThread_Head(&temp, t);
//加入頭結點,並線索化
-
printf(
"輸出中序二叉樹的內容:\n");
-
InOrderThraverse_Thr(temp);
-
-
printf(
"\n");
-
return
0;
-
}
轉自:[https://blog.csdn.net/u014492609/article/details/40477795](https://blog.csdn.net/u014492609/article/details/40477795)