資料結構系列 - 線索二叉樹
1. 前言
n個結點的二叉連結串列中含有n+1個空指標域。利用二叉連結串列中的空指標域,存放指向結點在某種遍歷次序下的前趨和後繼結點的指標(這種附加的指標稱為" 線索 ")。
這種加上了線索的二叉連結串列稱為 線索連結串列 ,相應的二叉樹稱為 線索二叉樹 (Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種。
注意:線索連結串列解決了二叉連結串列找左、右孩子困難的問題,出現了無法直接找到該結點在某種遍歷序列中的前趨和後繼結點的問題。
2. 二叉樹線索化的定義
n個結點的二叉樹,採用鏈式儲存結構時,有n+1個空鏈域,利用這些空鏈域存放指向結點的直接前驅和直接後繼的指標。若規定結點有左子樹,則其lchild域指示其左子女,否則令lchild域指示其前驅;若結點有右子樹,則其rchild域指示其右子女,否則令rchild域指示其後繼。為了避免混淆,我們需改變結點結構,在二叉儲存結構的結點結構上增加兩
個標誌域,這樣,每個結點的儲存結構如下:
- 線索連結串列:以上述結點結構構成的二叉連結串列作為二叉樹儲存結構的連結串列。
- 線索:指向結點前驅和後繼的指標。
- 線索二叉樹:每個結點上加上線索的二叉樹。
- 線索化:對二叉樹以某種次序遍歷使其變為線索二叉樹的過程。
建立線索二叉樹,或者說,對二叉樹線索化,實質上就是遍歷一棵二叉樹,在遍歷的過程中,檢查當前結點的左、右指標域是否為空。如果為空,將它們改為指向前驅結點或後繼結點的線索。
線索連結串列中的結點結構為:
圖 線索連結串列中的結點結構
其中:ltag和rtag是增加的兩個標誌域,用來區分結點的左、右指標域是指向其左、右孩子的指標,還是指向其前趨或後繼的線索。
線索二叉樹的儲存結構型別定義描述如下:
/*Link=0表示指向孩子結點,Thread=1表示指向前驅結點或後繼結點*/ typedef enmu {Link, Thread} PointerTag; /*線索二叉樹儲存結構定義*/ typedef struct Node { DataType data; /*資料域*/ struct Node *lchild, rchild; /*指向左孩子結點或右孩子結點的指標*/ PointerTag ltag, rtag; /*標誌域*/ }*BiThrTree, BiThrNode;
3. 線索二叉樹的表示
【例】下面(a)圖所示的中序線索二叉樹,其線索連結串列如下面(b)圖所示。
圖 中序線索二叉樹及其儲存結構
注意:
圖中的實線表示指標,虛線表示線索。
結點C的左線索為空,表示C是中序序列的開始結點,無前趨;
結點E的右線索為空,表示E是中序序列的終端結點,無後繼。
線索二叉樹中,一個結點是葉結點的充要條件為:左、右標誌均是1。
4. 二叉樹的線索化
(1)線索化和線索化實質
將二叉樹變為線索二叉樹的過程稱為線索化。
按某種次序將二叉樹線索化的實質是:按該次序遍歷二叉樹,在遍歷過程中用線索取代空指標。
(2).二叉樹的中序線索化
① 分析
演算法與中序遍歷演算法類似。只需要將遍歷演算法中訪問結點的操作具體化為建立正在訪問的結點與其非空中序前趨結點間線索。
該演算法應附設一個指標pre始終指向剛剛訪問過的結點(pre的初值應為NULL),而指標p指示當前正在訪問的結點。結點*pre是結點*p的前趨,而*p是*pre的後繼。
② 將二叉樹按中序線索化的演算法
typedef enum { Link,Thread} PointerTag; //列舉值Link和Thread分別為0,1 typedef struct node{ DataType data; PointerTag ltag,rtag; //左右標誌 Struct node *lchild,*rchild; } BinThrNode;\\線索二叉樹的結點型別 typedef BinThrNode *BinThrTree; BinThrNode *pre=NULL; //全域性量 void lnorderThreading(BinThrTree p) {//將二叉樹p中序線索化 if(p){ //p非空時,當前訪問結點是*p InorderThreading(p->lchild); //左子樹線索化 //以下直至右子樹線索化之前相當於遍歷演算法中訪問結點的操作 p->ltag=(p->lchild)?Link:Thread; //左指標非空時左標誌為Link //(即0),否則為Thread(即1) p->rtag=(p->rchild)?Link:Thread; *(pre){ //若*p的前趨*pre存在 if(pre->rtag==Thread) //若*p的前趨右標誌為線索 pre->rchild=p; //令*pre的右線索指向中序後繼 if(p->ltag==Thread) //*p的左標誌為線索 p->lchild=pre; //令*p的左線索指向中序前趨 } // 完成處理*pre的線索 pre=p; //令pre是下一訪問結點的中序前趨 InorderThreeding(p->rehild); //右子樹線索化 }//endif } //InorderThreading
③ 演算法分析
和中序遍歷演算法一樣,遞迴過程中對每結點僅做一次訪問。因此對於n個結點的二叉樹,演算法的時間複雜度亦為O(n)。
5. 遍歷線索二叉樹
遍歷某種次序的線索二叉樹,只要從該次序下的開始結點開發,反覆找到結點在該次序下的後繼,直至終端結點。
遍歷中序線索二叉樹演算法:
void TraverseInorderThrTree(BinThrTree p) { //遍歷中序線索二叉樹 if(p){//樹非空 while(p->ltag==Link) p=p->lchild; //從根往下找最左下結點,即中序序列的開始結點 do{ printf("%c",p->data); //訪問結點 p=InorderSuccessor(p); //找*p的中序後繼 }while(p) }//endif }//TraverselnorderThrTree
分析:
① 中序序列的終端結點的右線索為空,所以do語句的終止條件是p==NULL。
② 該演算法的時間複雜性為O(n)。因為是非遞迴演算法,常數因子上小於遞迴的遍歷演算法。因此,若對一棵二叉樹要經常遍歷,或查詢結點在指定次序下的前趨和後繼,則應採用線索連結串列作為儲存結構為宜。
③ 以上介紹的線索二叉樹是一種全線索樹(即左右線索均要建立)。許多應用中只要建立左右線索中的一種。
④ 若線上索連結串列中增加一個頭結點,令頭結點的左指標指向根,右指標指向其遍歷序列的開始或終端結點會更方便。