基本概念
遍歷二叉樹是對非線性結(jié)構(gòu)結(jié)點的線性化過程,由此得到的遍歷序列中,每個結(jié)點有且僅有一個前驅(qū)和后繼(除了序列中的第一個和最后一個結(jié)點)。
原始二叉鏈表的結(jié)點結(jié)構(gòu)僅包含數(shù)據(jù)元素信息和左右指針域,若在結(jié)點結(jié)構(gòu)中增加前驅(qū)和后繼的指針域,則該存儲結(jié)構(gòu)稱為線索二叉樹。
雖然可以直接增加兩個指針域來實現(xiàn)這種結(jié)構(gòu),但這樣會使結(jié)構(gòu)的存儲密度大大降低。(存儲密度 = 數(shù)據(jù)元素本身占用的存儲量 / 結(jié)點結(jié)構(gòu)占用的存儲量)
要想利用空指針域來存放前驅(qū)、后繼的地址,還需要將前驅(qū)、后繼和左、右子樹根結(jié)點區(qū)分開。因此我們新增兩個標(biāo)志域:LTag和RTag(整型),1表示有子樹,0表示無子樹。
1)當(dāng)LTag = 0時,lchild域指示結(jié)點的左子樹根結(jié)點;當(dāng)LTag = 1時,lchild域指示結(jié)點在某個遍歷序列中的前驅(qū)。
2)當(dāng)RTag = 0時,rchild域指示結(jié)點的右子樹根結(jié)點;當(dāng)RTag = 1時,rchild域指示結(jié)點在某個遍歷序列中的后繼。
指向前驅(qū)、后繼的指針稱為線索。
typedef struct ThBinode
{
Elemtype data;
struct ThBinode *lchild, *rchild;
int LTag, RTag;
} ThBinode, *ThBiTree;
構(gòu)造線索二叉樹
構(gòu)造線索二叉樹實質(zhì)上是將二叉鏈表中的空指針域改為前驅(qū)、后繼指針域。
又因為前驅(qū)、后繼的信息只有在遍歷的時候才能得到,所以線索化的過程就是在遍歷的時候修改空指針的過程。
為了記下遍歷過程中訪問結(jié)點的先后關(guān)系,附設(shè)一個指針pre指向剛剛訪問過的結(jié)點(即序列中的前一個結(jié)點),而指針p指向當(dāng)前訪問的結(jié)點。
/*--------以結(jié)點p為根的子樹的中序線索化--------*/
void InThreading(ThBiTree p)
{
if(p)
{
InThreading(p->lchild); /*左子樹遞歸線索化*/
if(!p->lchild) /*如果沒有左子樹*/
{
p->LTag = 1; /*設(shè)置前驅(qū)*/
p->lchild = pre;
}
else p->RTag = 0; /*有左子樹*/
if(!pre->rchild) /*如果沒有右子樹*/
{
pre->RTag = 1; /*設(shè)置后繼*/
pre->rchild = p;
}
else pre->rchild = 0;
pre = p;
InThreading(p->rchild); /*右子樹遞歸線索化*/
}
}
/*---------帶頭結(jié)點的二叉樹的中序線索化--------*/
void InOrderThreading(ThBiTree &tb, ThBiTree t)
{
tb = new ThBinode; /*建立頭結(jié)點*/
tb->LTag = 0; /*頭結(jié)點有左孩子,為根結(jié)點*/
tb->Rtag = 1; /*頭結(jié)點無右孩子,后繼線索為中序遍歷的最后一個結(jié)點*/
tb->rchild = tb; /*初始化時后繼為頭結(jié)點本身*/
if(!t) tb->lchild = tb; /*若二叉樹為空,左孩子也為頭結(jié)點本身*/
else
{
tb->lchild = t; pre = tb; /*頭結(jié)點的作用體現(xiàn),統(tǒng)一根結(jié)點與其他結(jié)點的處理方式*/
InThreading(t); /*對二叉樹中序線索化*/
pre->RTag = 1; /*線索化結(jié)束后,pre為二叉樹的最右結(jié)點*/
pre->rchild = tb; /*使其右線索指向頭結(jié)點*/
tb->rchild = pre; /*將頭結(jié)點的后繼從它本身改為最右結(jié)點*/
}
}
遍歷線索二叉樹
由于有了結(jié)點的前驅(qū)和后繼信息,線索二叉樹的遍歷和指定次序下查找結(jié)點的前驅(qū)和后繼算法都變得簡單。
線索二叉樹的遍歷不需要設(shè)棧,避免了頻繁的進(jìn)棧、出棧,因此在時間和空間上都較遍歷二叉樹節(jié)省。
因此,若需要經(jīng)常查找結(jié)點在所遍歷線性序列中的前驅(qū)和后繼,則采用線索鏈表作為存儲結(jié)構(gòu)。
1)中序線索二叉樹
查找p的前驅(qū):查左線索;若無左線索,結(jié)點的前驅(qū)是遍歷左子樹時訪問的最后一個結(jié)點。
查找p的后繼:查右線索;若無右線索,結(jié)點的后繼是遍歷右子樹時訪問的第一個結(jié)點。
2)先序線索二叉樹
查找p的前驅(qū):查左線索;若無左線索,結(jié)點的前驅(qū)是結(jié)點的雙親結(jié)點,或是先序遍歷其雙親結(jié)點左子樹時最后訪問的結(jié)點。
查找p的后繼:查右線索;若無右線索,結(jié)點的后繼必為結(jié)點的左子樹(若存在)或右子樹根結(jié)點。
3)后序線索二叉樹
1. 查找p的前驅(qū):查左線索;若無左線索,且無右線索時,結(jié)點的前驅(qū)是右子樹根結(jié)點;若無左線索,但是有右線索時,結(jié)點的前驅(qū)是左子樹根結(jié)點。
2. 查找p的后繼,這種查找比較復(fù)雜,分4類情況討論:
若p為二叉樹的根結(jié)點,后繼為空;
若p為右子樹根結(jié)點,后繼為雙親結(jié)點;
若p為左子樹根結(jié)點,且無右兄弟,后繼為雙親結(jié)點;
若p為左子樹根結(jié)點,且有右兄弟,后繼為后序遍歷雙親結(jié)點右子樹時訪問的第一個結(jié)點。
由上述情況可知,在先序線索二叉樹上找前驅(qū)和在后序線索二叉樹上找后繼都比較復(fù)雜。
遍歷線索二叉樹的時間復(fù)雜度為O(n),與遞歸或非遞歸遍歷二叉鏈表一樣,但前者的空間復(fù)雜度為O(1),而后者為O(n),因為遍歷線索二叉樹不需要棧。
/*----------遍歷中序線索二叉樹----------*/
void InOrderTraverse(ThBiTree t)
{/*t指向線索二叉樹的頭結(jié)點,而頭結(jié)點的左指針指向二叉樹的根結(jié)點*/
ThBinode *p = t->lchild; /*使p指向根結(jié)點*/
while(p != t) /*若線索二叉樹不為空或遍歷未結(jié)束*/
{
while(p->LTag == 0) p=p->lchild; /*沿左孩子往下,定位*/
cout<<p->data; /*訪問左子樹為空的結(jié)點*/
while(p->RTag == 1 && p->rchild != t) /*若有右線索,且右線索不為頭結(jié)點*/
{
p = r->rchild;
cout<<p->data; /*沿右線索訪問后繼結(jié)點*/
}
p = p->rchild; /*轉(zhuǎn)向p的右子樹*/
}
}
原文鏈接:https://blog.csdn.net/ha1f_awake/article/details/85186310