現有一棵結點數目為n的二叉樹,采用二叉鏈表的形式存儲。對于每個結點均有指向左右孩子的兩個指針域,而結點為n的二叉樹一共有n-1條有效分支路徑。那么,則二叉鏈表中存在2n-(n-1)=n+1個空指針域。那么,這些空指針造成了空間浪費。
例如:圖2.1所示一棵二叉樹一共有10個結點,空指針^有11個。
此外,當對二叉樹進行中序遍歷時可以得到二叉樹的中序序列。例如:圖2.1所示二叉樹的中序遍歷結果為HDIBJEAFCG,可以得知A的前驅結點為E,后繼結點為F。但是,這種關系的獲得是建立在完成遍歷后得到的,那么可不可以在建立二叉樹時就記錄下前驅后繼的關系呢,那么在后續尋找前驅結點和后繼結點時將大大提升效率。
線索化
現將某結點的空指針域指向該結點的前驅后繼,定義規則如下:
若結點的左子樹為空,則該結點的左孩子指針指向其前驅結點。
若結點的右子樹為空,則該結點的右孩子指針指向其后繼結點。
這種指向前驅和后繼的指針稱為線索。將一棵普通二叉樹以某種次序遍歷,并添加線索的過程稱為線索化。
按照規則將圖2.1所示二叉樹線索化后如圖所示:
圖中黑色點畫線為指向后繼的線索,紫色虛線為指向前驅的線索。
可以看出通過線索化,既解決了空間浪費問題,又解決了前驅后繼的記錄問題。
線索化帶來新問題
可以將一棵二叉樹線索化為一棵線索二叉樹,那么新的問題產生了。我們如何區分一個結點的lchild指針是指向左孩子還是前驅結點呢?例如:對于圖所示的結點E,如何區分其lchild的指向的結點J是其左孩子還是前驅結點呢?
為了解決這一問題,現需要添加標志位ltag,rtag。并定義規則如下:
ltag為0時,指向左孩子,為1時指向前驅
rtag為0時,指向右孩子,為1時指向后繼
添加ltag和rtag屬性后的結點結構如下:
上圖所示線索二叉樹轉變為圖所示的二叉樹。
題
答案:b
線索二叉樹結點數據結構
//#define Link 0//指針標志
//#define Thread 1//線索標志
typedef char TElemType;
//中序線索二叉樹
typedef enum PointerTag {Link, Thread};//結點的child域類型,link表示是指針,指向孩子結點,thread表示是線索,指示前驅或后繼結點
//定義結點數據結構
typedef struct ThrBiNode{ TElemType data; ThrBiNode *lchild, *rchild;//左右孩子指針 PointerTag lTag, rTag;//左右標志
}ThrBiNode, *ThrBiTree;
中序遍歷建立線索二叉樹
中序遍歷的方法已經在第一篇二叉基礎樹中講解過,那么實現線索化的過程就是在中序遍歷同時修改結點空指針的指向。
采用中序遍歷的訪問順序實現一棵二叉樹的線索化過程代碼如下:
//中序遍歷進行中序線索化
void inThreading(ThrBiTree T, ThrBiTree &pre){ if(T){ inThreading(T->lchild, pre);//左子樹線索化 if(!T->lchild){//當前結點的左孩子為空 T->lTag = Thread; T->lchild = pre; }else{ T->lTag = Link; } if(!pre->rchild){//前驅結點的右孩子為空 pre->rTag = Thread; pre->rchild = T; }else{ pre->rTag = Link; } pre = T; inThreading(T->rchild, pre);//右子樹線索化 }
}
加上頭結點,遍歷線索二叉樹
加上線索的二叉樹結構是一個雙向鏈表結構,為了便于遍歷線索二叉樹,我們為其添加一個頭結點,頭結點左孩子指向原二叉樹的根結點,右孩子指針指向中序遍歷的最后一個結點。同時,將第一個結點左孩子指針指向頭結點,最后一個結點的右孩子指針指向頭結點。
上圖所示線索二叉樹添加頭結點后如圖所示:
帶有頭結點的線索二叉樹遍歷代碼如下:
//T指向頭結點,頭結點的lchild鏈域指針指向二叉樹的根結點
//中序遍歷打印二叉線索樹T(非遞歸算法)
void inOrderTraversePrint(ThrBiTree T){ ThrBiNode *p = T->lchild;//p指向根結點 while(p != T){//空樹或遍歷結束時,p == T while(p->lTag == Link){ p = p->lchild; } //此時p指向中序遍歷序列的第一個結點(最左下的結點) printf("%c ", p->data);//打印(訪問)其左子樹為空的結點 while(p->rTag == Thread && p->rchild != T){ p = p->rchild; printf("%c ", p->data);//訪問后繼結點 } //當p所指結點的rchild指向的是孩子結點而不是線索時,p的后繼應該是其右子樹的最左下的結點,即遍歷其右子樹時訪問的第一個節點 p = p->rchild; } printf("\n");
}
結語
線索二叉樹充分利用了指針空間,同時又便于尋找結點的前驅結點和后繼結點。線索二叉樹適用于經常需要遍歷尋找結點前驅或者后繼結點的二叉樹。