原文地址:http://www.cnblogs.com/Security-Darren/p/4716082.html
轉載務必注明出處!
線索二叉樹的思想來源於二叉樹的存儲結構中,存在一些空的指針域,因此是否能夠將這些空間利用起來,存儲一些關於節點間先后順序的信息,由此產生了線索二叉樹。線索二叉樹中,線索反映前驅、后繼的關系,而指針則體現左右子樹。
以二叉鏈表為例,線索二叉樹存儲結構上的特點是添加標識符,表明左右指針域究竟存的是指向前驅和后繼的線索,還是指向左右子樹的指針;
線索二叉樹的優勢是一旦對一棵二叉樹建立了相應的線索結構,當以后使用特定的方式遍歷這課樹時,可以避免遞歸方式和非遞歸方式(利用棧)帶來的空間開銷。
構建和遍歷線索二叉樹的思路如下:
- 首先構造一棵二叉樹(構造二叉樹可以使用任意順序:先序、中序、后續均可);
- 按照一定的順序線索化這棵二叉樹
- 然后按照相同的順序遍歷該二叉樹,就可以利用上一步構建的線索信息。
這里以帶線索的二叉鏈表方式作為存儲結構,先序創建一棵二叉樹,然后中序線索化這棵二叉樹,得到的是一棵中序線索二叉樹,此后再中序遍歷該二叉樹,就可以使用這里的線索信息,不用額外的棧空間。
原文地址:http://www.cnblogs.com/Security-Darren/p/4716082.html
轉載務必注明出處!
該例子共包括一個biThrTree.h文件和一個biThrTree..c文件
1. biThrTree.h,節點存儲結構
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define OK 1 5 #define ERROR 0 6 #define OVERFLOW -1 7 8 typedef int Status; 9 10 typedef char TElemType; 11 12 typedef enum {Link, Thread} PointerTag; 13 14 typedef struct BiThrNode{ 15 TElemType data; 16 PointerTag LTag, RTag; // 枚舉型的特點,LTag和RTag初始為0,即Link 17 struct BiThrNode *lchild, *rchild; 18 }BiThrNode, *BiThrTree; 19 20 BiThrTree pre = NULL; // 在線索化二叉樹中用到,記錄遍歷過程中,一個節點的前驅
由於枚舉型的變量聲明是如果沒有賦初值,默認設置初值為0,所以等於默認每個BiThrNode實例的LTag和RTag都是Link。
2. biThrTree.h,簡單的遍歷操作——打印
Status PrintElement(TElemType e){ printf("%c", e); return OK; }
這里定義一個最簡單的遍歷操作作為示意,僅僅打出被遍歷元素包含的數據,我們的線索二叉樹遍歷函數接受指向函數的指針,可以方便地改變遍歷函數中進行的遍歷操作。
3. biThrTree.h,先序創建二叉樹
創建二叉樹可以采用任何順序,這里以先序創建為例。
1 BiThrTree CreateBiThrTree() 2 { 3 TElemType ch; 4 BiThrTree T; 5 6 scanf("%c", &ch); 7 getchar(); // “吃掉”每次輸入到緩沖區中的回車,不能缺少 8 if (ch == '$') 9 T = NULL; 10 else{ 11 T = (BiThrTree)malloc(sizeof(BiThrNode)); 12 if(!T) 13 exit(OVERFLOW); 14 T->data = ch; // 11 - 14行代碼與下面兩個遞歸調用的位置體現了創建函數采用的遍歷順序 15 printf("Input lchild of %c:\n", ch); 16 T->lchild = CreateBiThrTree(); 17 printf("Input rchild of %c:\n", ch); 18 T->rchild = CreateBiThrTree(); 19 } 20 return T; 21 }
這里以美元符號"$"表示空指針,告訴程序一個節點沒有左子樹或右子樹。
創建一棵二叉樹與單純以某種順序寫出二叉樹的遍歷結果不同,在創建二叉樹時,要以某個符號指定NULL指針,這樣程序才知道哪里是葉子節點,哪里不能再向左/右,必須回退了。
具體的例子在本文最后的運行示例中給出。
4. biThrTree.h,中序線索化已經創建的二叉樹
線索化針對的是已經創建好的二叉樹,將其中的空指針域利用起來指向前驅后繼的過程就是線索化。
1 void InThreading(BiThrTree p){ 2 if(p){ 3 InThreading(p->lchild); 4 if(!(p->lchild)){ 5 p->LTag = Thread; 6 p->lchild = pre; 7 } 8 if(!(pre->rchild)){ 9 pre->RTag = Thread; 10 pre->rchild = p; 11 } 12 pre = p; 13 InThreading(p->rchild); 14 } 15 } 16 17 Status InOrderThreading(BiThrTree *Thrt, BiThrTree T){ 18 (*Thrt) = (BiThrTree)malloc(sizeof(BiThrNode)); //頭節點不同於二叉樹的根節點 19 if(!(*Thrt)) 20 exit(OVERFLOW); 21 (*Thrt)->RTag = Thread; 22 (*Thrt)->rchild = *Thrt; 23 (*Thrt)->LTag = Link; 24 25 if(!T) 26 (*Thrt)->lchild = *Thrt; 27 else{ 28 (*Thrt)->lchild = T; 29 pre = *Thrt; // 全局的pre,這里初始化指向頭結點 30 InThreading(T); 31 pre->rchild = *Thrt; // 此時pre停留在中序遍歷二叉樹時的最后一個節點上 32 pre->RTag = Thread; 33 (*Thrt)->rchild = pre; 34 } 35 return OK; 36 }
線索化只針對原來是空的指針域,一個節點如果左指針域為空,那么線索化后該指針域指向其遍歷過程中的前驅;類似的,如果其右指針域為空,線索化后右指針域指向其遍歷過程的后繼。線索化后的二叉鏈樹中不存在空的指針域。
考慮這段代碼中的 4 -12 行,一方面線索化的過程只關注已經創建好的二叉樹中那些空的指針域;另一方面,上文關於存儲結構的介紹已經提到,LTag和RTag的初始值都是Link,所以當中序遍歷到達某個節點時,非空的指針域已經不需要再設置Link。
4 -12 行代碼中,對於中序遍歷到的每一個節點,如果它的左指針域為空,那么我們在這次遍歷中將其置為線索,而如果它的右指針域為空,我們在遍歷走向下一個節點時線索化上一個節點的右指針域。
這樣帶來一個問題,一棵樹中最后一個被遍歷到的節點的右指針域沒有被線索化,這也就是 31 - 33 行代碼的工作。
InOrderThreading() 創建一個額外的頭結點 Thrt ,頭結點不同於二叉樹的根節點。頭結點用來解決遍歷二叉樹過程中,第一個遍歷到的節點沒有前驅和最后一個遍歷到的節點沒有后繼的問題。
對於一棵非空的二叉樹,Thrt的LTag為Link,左指針指向二叉樹的根節點;遍歷二叉樹時的第一個節點,如果左指針域為空,則將其指向Thrt。Thrt的RTag為Thread,Thrt的右指針指向遍歷二叉樹時的最后一個節點,同時如果最后一個節點的右指針域為空,則將其指向Thrt。
從而中序線索化時,將會形成一個從Thrt出發,再回到Thrt的閉環,這也是線索化的優勢。
5. biThrTree.h,中序遍歷已經線索化的二叉樹
采用某種順序線索化的二叉樹,可以相應地采用同樣的順序進行遍歷,中序遍歷中序線索二叉樹,不需要額外的棧空間支持,根據線索即可完成遍歷,適用於需要經常對樹進行遍歷操作,或者給定某個節點,需要判斷其前驅或者后繼的情境。
Status InOrderTraverse_Thr( BiThrTree Thrt, Status (*Visit)(TElemType e)){ BiThrTree p; p = Thrt->lchild; while(p != Thrt){ while(p->LTag == Link) p = p->lchild; //先走到最左端,開始 if(!(*Visit)(p->data)) return ERROR; while(p->RTag == Thread && p != Thrt){ p = p->rchild;// 如果右指針是個線索,直接向后遍歷后繼 (*Visit)(p->data); } if (p != Thrt) // 如果不加區分就走向右子樹,很有可能出現死循環,因為 p剛剛停到Thrt,還沒進行下一次循環判斷,就立刻指向Thrt的后繼,循環可能不會終止 p = p->rchild; } return OK;
線索化和利用線索的遍歷必須遵循相同的順序。
6. biThrTree.c,主程序進行功能測試
1 #include"biThrTree.h" 2 3 // 1. Create Binary Tree with Pre-Order input(Creation can be in any order) 4 // 2. In-Order Threading this tree (How to threading the tree depends on in what 5 // order you are goning to traverse the tree.) 6 // 3. then In-Order Traverse this tree 7 8 int main(int argc, char *argv[]){ 9 BiThrTree T, temp; 10 11 printf("Creating Binary Thread Tree.\n"); 12 T = CreateBiThrTree(); 13 if(!T) 14 return OVERFLOW; 15 printf("Binary Thread Tree Created.\n"); 16 17 if(!InOrderThreading(&temp, T)) 18 return ERROR; 19 20 printf("In Order Traversing the Binary Thread Tree:\n"); 21 if(!InOrderTraverse_Thr(temp, &PrintElement)) 22 return ERROR; 23 printf("\nIn Order Traversing Accomplished.\n"); 24 25 return OK; 26 }
主程序首先創建一棵二叉樹,然后對其進行中序線索化,最后中序遍歷,打印遍歷結果。
下面以輸入:
ABC$$DE$G$$F$$$
為例先序構建一棵二叉樹,這棵二叉樹的樣子如下:
A / B / \ C D / \ E F \ G
在構建二叉樹時,必須指定空指針的位置。
在GCC下編譯,程序執行的過程如下:
1 ./a.out 2 Creating Binary Thread Tree. 3 A 4 Input lchild of A: 5 B 6 Input lchild of B: 7 C 8 Input lchild of C: 9 $ 10 Input rchild of C: 11 $ 12 Input rchild of B: 13 D 14 Input lchild of D: 15 E 16 Input lchild of E: 17 $ 18 Input rchild of E: 19 G 20 Input lchild of G: 21 $ 22 Input rchild of G: 23 $ 24 Input rchild of D: 25 F 26 Input lchild of F: 27 $ 28 Input rchild of F: 29 $ 30 Input rchild of A: 31 $ 32 Binary Thread Tree Created. 33 In Order Traversing the Binary Thread Tree. 34 CBEGDFA 35 In Order Traversing Accomplished.
最后介紹一個中序線索化二叉樹的優點,即中序線索化后,對於二叉樹中的任何一個節點,我們都可以快速地求它的前驅和后繼:
前驅:
- 左指針是Thread,lchild就是前驅
- 左指針是Link,lchild是其左子樹,但是可以找到前驅:中序遍歷是先左子樹,對於左子樹而言,最后遍歷左子樹的右子樹,所以此時一個節點的前驅是其左子樹最右下的節點,沿着左子樹的右Link一直向下,遇到RTag為Thread的就是目標節點的前驅
后繼:
- 右指針是Thread,則rchild就是后繼
- 右指針是Link,rchild是右子樹,但是可以找到后繼:中序遍歷時,后遍歷某個節點的右子樹,而該節點的后繼就是其右子樹中最先被遍歷到的,即其右子樹最左下的節點,沿着右子樹的左Link一直向左下,直到遇到LTag為Thread的就是目標節點的后繼。
所以,中序線索化還是很便於索引樹中任意一個節點的前驅后繼關系的。