咱們在上一章中,學習了二叉樹的數據結構。由於二叉樹的特殊性,它不一樣於普通的樹,因此可使用順序存儲結構來存儲。可是,用順序存儲結構會存在浪費空間的弊端。以後,咱們學習了二叉鏈表。用鏈式存儲結構存儲樹,結點結構爲一個數據域data,兩個指針域lchild、rchild。樹的數據結構講完了,可是沒有講怎樣生成一棵二叉樹。今天,咱們來學習怎樣生成一棵二叉樹。算法
1.生成二叉樹:數據結構
要想生成一棵二叉樹,咱們須要清楚結點是否有左孩子、右孩子結點。爲了搞清楚這一點,咱們須要對二叉樹進行擴展,通過擴展後的二叉樹,稱爲擴展二叉樹。函數
咱們之前序順序來生成上圖的二叉樹,前序順序爲AB#D##C##.學習
/*按前序輸入二叉樹中結點的值(一個字符)*/ /*#表示空樹*/ CreateBirTree(BirTree T) { TElemType ch; //結點值類型 Scanf("%c",&ch); //接收輸入參數 if(ch=="#") //判斷是不是空樹 *T==Null // 樹爲空 else { *T=(BirTree) malloc(sizeof(BiTNode)); //內存中開闢空間,存放結點 if(!*T) exit(OVERFLOW); (*T)->data=ch; /*生成根結點*/ CreateBiTree(&(*T)->lchild); /*構造左子樹*/ CreateBiTree(&(*T)->rchild);/*構造右子樹*/ } }
上面就是咱們的前序建立二叉樹的算法代碼,咱們一樣可使用後序、中序建立二叉樹。算法代碼,與上面大體同樣,只是調換一下遞歸順序。spa
2.線索二叉樹:指針
數據結構之因此重要,是由於好的數據結構可使計算機運做更高效。好的數據結構,應該是運行時間短、佔用空間少。二叉鏈表中,有不少空的指針域。這些沒用的指針域在內存中佔有必定的空間,這是一種浪費。以下圖:code
上圖的中序遍歷順序爲:HDIBJEAFCG,經過中序遍歷順序,咱們知道I的前驅是D,後繼是B,F的前驅是A,後繼是C。可是,這是在咱們以中序遍歷整棵樹以後獲得的。每次,須要找某個結點的前驅、後繼,咱們就須要遍歷整棵樹,這是時間上的浪費。blog
經過上面的空間、時間兩方面的考慮,咱們須要利用那些沒用的空指針域。咱們能夠在建立二叉樹時,利用這些空的指針域存放結點的前驅、後繼。這樣的話,只需一次遍歷就能夠知道遍歷次序。以下圖:遞歸
咱們利用lchild存放前驅結點、rchild存放後繼結點。這樣咱們就能夠將二叉鏈表的缺點彌補。lchild、rchild指向前驅、後繼結點,咱們稱這樣的指針爲線索,加上線索的二叉樹,稱爲線索二叉樹。咱們以二叉樹某種次序遍歷使其變爲線索二叉樹的過程稱作是線索化。內存
可是問題又來了,咱們怎麼會知道結點的lchild、rchild是空的。因此,咱們須要增添兩個記錄指針域狀態的變量。
當ltag=0、rtag=0時,指針指向左子樹、右子樹。
當ltag=一、rtag=1時,指針指向前驅、後繼。
整理後的線索二叉圖。
3.線索二叉樹結構實現:
線索二叉樹結點結構:
/*二叉樹的二叉線索存儲結構定義*/ typedef enum {Link,Thread} pointerTag;/*Link==0表示指向左右孩子指針.Thread=1,表示指向前驅、後繼*/ typedef struct BiThrNode /*二叉線索存儲結點結構*/ { TElemType data; /*結點數據*/ struct BiThrNode *lchild、*rchild; /*左右孩子指針*/ pointerTag Ltag; pointerTag Rtag; /*左右標誌*/ }BiThrNode,*BiThrTree;
其實線索化的過程,就是在遍歷二叉樹時,將空指針指向前驅、後繼的過程。
咱們看一下中序遍歷線索化的過程函數代碼:
BiThrTree pre; /*全局變量,始終指向剛剛訪問過的結點*/ /*中序遍歷進行中序線索化*/ void InThreading(BiThrTree p) { if(p) //判斷樹是否爲空 { InThreading(p->lchild); /*遞歸左子樹線索化*/ if!(p->lchild) /*判斷左孩子結點是否爲空*/ { p->Ltag=Thread; /*指針域存放前驅*/ p->lchild=pre; /*將pre賦給p的前驅指針*/ } if(!pre->rchild) /*判斷剛剛訪問過的結點是否有右孩子*/ { pre->Rtag=Thread; /*指針域存放後繼*/ pre->rchild=p; } pre=p; /*將p賦給Pre*/ InThreading(p->rchild); /*遞歸右子樹線索化*/ } }
pre,始終指向剛剛訪問過的結點。函數首先,判斷p是否爲空。不爲空,遞歸左子樹。判斷結點是否有左孩子,也就是判斷結點的lchild指針域是否爲空。當lchild指針域不爲空,將標識設置爲Thread,代表此結點的lchild指向前驅。將pre賦給p的lchild。由於後繼元素,尚未遍歷到。因此只能對Pre進行後繼賦值。最後將p賦值給pre,再遞歸右子樹線索化。咱們能夠發現,中序線索化的函數,與中序遍歷函數很類似。只是把輸出操做,改成向指針域賦值。我沒呢對線索二叉樹操做,其實就是對一個雙向鏈表操做。
咱們看一下遍歷線索二叉樹的函數:
/*中序遍歷二叉鏈表*/ /*T指向頭結點,頭結點的右鍵rchild指向中序遍歷的最後一個結點*/ Status InOrderTraverse_Thr(BiTree T) { BiThrTree p; p=T;/*p指向根結點*/ while(p!=T) /*判斷p是否爲空樹、是否遍歷完整棵樹*/ { 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; } return ok; }
這就是咱們今天講的內容了。