1.樹(shù)和二叉樹(shù)的定義
(1) 樹(shù)的定義
樹(shù)是n (n≥0) 個(gè)結(jié)點(diǎn)的有限集。 n=0 時(shí)稱(chēng)為空樹(shù)。在任意一棵非空樹(shù)中:
- 有且僅有一個(gè)特定的稱(chēng)為根(Root) 的結(jié)點(diǎn);
- 當(dāng)n>1 時(shí),除了根結(jié)點(diǎn)以外其余結(jié)點(diǎn)可分為m(m>0) 個(gè)互不相交的有限集T1、 T2、 ……、 Tm, 其中每一個(gè)集合本身又是一棵樹(shù),并且稱(chēng)為根的子樹(shù)(SubTree)。
(2) 樹(shù)的基本術(shù)語(yǔ)
結(jié)點(diǎn):樹(shù)中的一個(gè)獨(dú)立單元。
結(jié)點(diǎn)的度:結(jié)點(diǎn)擁有的子樹(shù)數(shù)稱(chēng)為結(jié)點(diǎn)的度。
樹(shù)的度:樹(shù)的度是樹(shù)內(nèi)各結(jié)點(diǎn)度的最大值。
葉子:度為0的結(jié)點(diǎn)稱(chēng)為葉子或者終端結(jié)點(diǎn)。
非終端結(jié)點(diǎn):度不為0的結(jié)點(diǎn)稱(chēng)為非終端結(jié)點(diǎn)或者分支結(jié)點(diǎn)。除根結(jié)點(diǎn)以外,非終端結(jié)點(diǎn)也稱(chēng)為內(nèi)部結(jié)點(diǎn)。
雙親和孩子:結(jié)點(diǎn)的子數(shù)的根稱(chēng)為該結(jié)點(diǎn)的孩子,相應(yīng)地,該結(jié)點(diǎn)稱(chēng)為孩子地雙親。
兄弟:同一個(gè)雙親地孩子之間互稱(chēng)為兄弟。
祖先:從根到該結(jié)點(diǎn)所經(jīng)分支上所有結(jié)點(diǎn)。
子孫:以某結(jié)點(diǎn)為根地子數(shù)中任一結(jié)點(diǎn)都稱(chēng)為該結(jié)點(diǎn)地子孫。
層次:結(jié)點(diǎn)地層次從根開(kāi)始定義起,根稱(chēng)為第一層,根地孩子為第二層。樹(shù)中任一結(jié)點(diǎn)層次等于其雙親結(jié)點(diǎn)地層次加1。
堂兄弟:雙親在同一層次地結(jié)點(diǎn)互為堂兄弟。
樹(shù)地深度:樹(shù)中結(jié)點(diǎn)地最大層次稱(chēng)為樹(shù)地深度或高度。
有序樹(shù)和無(wú)序樹(shù):如果將樹(shù)中結(jié)點(diǎn)地各個(gè)子樹(shù)看成從左至右是有次序地,不能互換,則稱(chēng)為有序樹(shù),否則稱(chēng)為無(wú)序樹(shù)。在有序樹(shù)中最左邊地子樹(shù)的根稱(chēng)為第一個(gè)孩子,最右邊的稱(chēng)為最后一個(gè)孩子。
森林:森林(Forest) 是m(m≥0) 棵互不相交的樹(shù)的集合。對(duì)樹(shù)中每個(gè)結(jié)點(diǎn)而言,其子樹(shù)的集合即為森林。
二叉樹(shù):是n(n≥0)個(gè)結(jié)點(diǎn)的有限集合,該集合或者為空集(稱(chēng)為空二叉樹(shù)),或者由一個(gè)根結(jié)點(diǎn)和兩棵互不相交的、分別稱(chēng)為根結(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的二叉樹(shù)組成。
(3) 二叉樹(shù)的定義
二叉樹(shù)是n個(gè)結(jié)點(diǎn)所構(gòu)成的集合,它或?yàn)榭諛?shù)(n=0),或?yàn)榉强諛?shù),對(duì)于非空樹(shù)T:
- 有且只有一個(gè)稱(chēng)為根的結(jié)點(diǎn)。
- 除根結(jié)點(diǎn)以外的其余結(jié)點(diǎn)分為兩個(gè)互不相交的子集T1和T2,分別稱(chēng)為T(mén)的左子樹(shù)和右子樹(shù),且T1和T2本身又是二叉樹(shù)。
二叉樹(shù)和樹(shù)的區(qū)別:
* 二叉樹(shù)每個(gè)結(jié)點(diǎn)至多只有兩顆子樹(shù)。
* 二叉樹(shù)的子樹(shù)有左右之分,其次序不能任意顛倒。
2. 二叉樹(shù)的性質(zhì)和存儲(chǔ)結(jié)構(gòu)
(1) 二叉樹(shù)的性質(zhì)
性質(zhì)1: 在二叉樹(shù)上的第i層至多有2^(i-1)個(gè)結(jié)點(diǎn)。(i>=1)
性質(zhì)2: 深度為k 的二叉樹(shù)至多有2^k -1個(gè)結(jié)點(diǎn)(k>=1)。
性質(zhì)3:對(duì)于任何一棵二叉樹(shù)T,如果其終端結(jié)點(diǎn)數(shù)為n0,度為2的結(jié)點(diǎn)數(shù)為n2,則n0=n2+1。
證明:
二叉樹(shù)結(jié)點(diǎn)總數(shù):n = n0+n1+n2;
除了根結(jié)點(diǎn)外,其余結(jié)點(diǎn)都有一個(gè)分支進(jìn)入,設(shè)B為分支總數(shù),則n = B + 1;由于這些分支是由度為1或2的結(jié)點(diǎn)射出,所以B=n1+2*n2
于是:n = n1+2*n2+1 => n0 = n2+1
滿二叉樹(shù)
:深度為k且含有2^k - 1個(gè)結(jié)點(diǎn)的二叉樹(shù)。
滿二叉樹(shù)的特點(diǎn):每一層上的結(jié)點(diǎn)數(shù)都是最大結(jié)點(diǎn)數(shù),即每一層i的結(jié)點(diǎn)數(shù)都具有最大值2^(i-1)。
對(duì)滿二叉樹(shù)結(jié)點(diǎn)進(jìn)行編號(hào),約定編號(hào)從根結(jié)點(diǎn)起,自上而下,自左至右。
完全二叉樹(shù):
深度為k對(duì),由n個(gè)結(jié)點(diǎn)的二叉樹(shù),當(dāng)且僅當(dāng)每一個(gè)結(jié)點(diǎn)都與深度為k的滿二叉樹(shù)中編號(hào)1至n的結(jié)點(diǎn)一一對(duì)應(yīng),稱(chēng)之為完全二叉樹(shù)。
完全二叉樹(shù)的特點(diǎn):
* 葉子結(jié)點(diǎn)只能在層次最大的兩層上出現(xiàn)。
* 對(duì)任一結(jié)點(diǎn),若其右分支下的子孫的最大層次為l,則其左分支下的子孫的最大層次必為l或l+1。
(2) 二叉樹(shù)的存儲(chǔ)結(jié)構(gòu)
1.順序存儲(chǔ)結(jié)構(gòu):使用一組地址連續(xù)的存儲(chǔ)單元來(lái)存儲(chǔ)數(shù)據(jù)元素,將二叉樹(shù)的結(jié)點(diǎn)依照自上而下,自左至右存儲(chǔ)結(jié)點(diǎn)元素。
#define MAXSIZE 100;
typedef TElemType SqBiTree[MAXSIZE];//0號(hào)單元存儲(chǔ)根結(jié)點(diǎn)
SqBiTree bt;
2.鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu):結(jié)點(diǎn)包含3個(gè)域:數(shù)據(jù)域,左右指針。
typedef struct BiTNode {
TElemType data;
Struct BiTree *lchild,*rchild;
}BiTNode,*BiTree;
3. 遍歷二叉樹(shù)和線索二叉樹(shù)
(1) 遍歷二叉樹(shù)
遍歷二叉樹(shù)是指按某條搜索路徑巡訪樹(shù)中每個(gè)結(jié)點(diǎn),使的每個(gè)結(jié)點(diǎn)均被訪問(wèn)一次,而且僅被訪問(wèn)一次。遍歷的實(shí)質(zhì)是對(duì)二叉樹(shù)進(jìn)行線性化的過(guò)程。
- 前序遍歷:規(guī)則是若二叉樹(shù)為空,則空操作返回,否則先訪問(wèn)根結(jié)點(diǎn),然后前序遍歷左子樹(shù), 再前序遍歷右子樹(shù)。如下最左圖,遍歷的順序?yàn)?ABDGHCEIF。
void PreOrderTraverse(BiTree T){
if(T)
{
printf("%c",T->data);/* 顯示結(jié)點(diǎn)數(shù)據(jù),可以更改為其它對(duì)結(jié)點(diǎn)操作 */
PreOrderTraverse(T->lchild); /* 再先序遍歷左子樹(shù) */
PreOrderTraverse(T->rchild); /* 最后先序遍歷右子樹(shù) */
}
}
- 中序遍歷:規(guī)則是若樹(shù)為空,則空操作返回,否則從根結(jié)點(diǎn)開(kāi)始(注意并不是先訪問(wèn)根結(jié)點(diǎn)),中序遍歷根結(jié)點(diǎn)的左子樹(shù),然后是訪問(wèn)根結(jié)點(diǎn),最后中序遍歷右子樹(shù)。 如下中左圖,遍歷的順序?yàn)? GDHBAEICF。
void InOrderTraverse(BiTree T){
if(T)
{
InOrderTraverse(T->lchild); /* 中序遍歷左子樹(shù) */
printf("%c",T->data);/* 訪問(wèn)根結(jié)點(diǎn) */
InOrderTraverse(T->rchild); /* 中序遍歷右子樹(shù) */
}
}
- 后序遍歷:規(guī)則是若樹(shù)為空,則空操作返回,否則從左到右先葉子后結(jié)點(diǎn)的方式遍歷訪問(wèn)左右子樹(shù),最后是訪問(wèn)根結(jié)點(diǎn)。 如下中右圖,遍歷的順序?yàn)? GHDBIEFCA。
void PostOrderTraverse(BiTree T){
if(T)
{
InOrderTraverse(T->lchild); /* 后序遍歷左子樹(shù) */
InOrderTraverse(T->rchild); /* 后序遍歷右子樹(shù) */
printf("%c",T->data);/* 訪問(wèn)根結(jié)點(diǎn) */
}
}
- 層序遍歷:規(guī)則是若樹(shù)為空, 則空操作返回,否則從樹(shù)的第一層,也就是根結(jié)點(diǎn)開(kāi)始訪問(wèn),從上而下逐層遍歷,在同一層中, 按從左到右的頗用才結(jié)點(diǎn)逐個(gè)訪問(wèn)。如下最右圖所示,遍歷的順序?yàn)?ABCDEFGHI。
1. 根據(jù)遍歷順序確定二叉樹(shù)
己知前序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹(shù);已知后序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹(shù);但已知前序和后序遍歷,是不能確定一棵二叉樹(shù)的。
2. 先序遍歷的順序建立二叉樹(shù)
(1) 掃描字符序列,讀入字符ch。
(2) 如果ch是一個(gè)“#”字符,則表明該二叉樹(shù)為空樹(shù),即T為NULL,否則執(zhí)行以下操作:
- 申請(qǐng)一個(gè)結(jié)點(diǎn)空間T。
- 將ch賦給T->data。
- 遞歸創(chuàng)建T的左子樹(shù)。
- 遞歸創(chuàng)建T的右子樹(shù)。
void CreateBiTree(BiTree &T)
{//先按次序輸入二叉樹(shù)中結(jié)點(diǎn)的值(一個(gè)字符),創(chuàng)建二叉鏈表示的二叉樹(shù)T
cin>>ch;
if(ch=='#') T=NULL; // 遞歸結(jié)束,建空樹(shù)
else //遞歸創(chuàng)建二叉樹(shù)
{
T = new BiTNode; /* 生成根結(jié)點(diǎn) */
T->data=ch; /* 根結(jié)點(diǎn)數(shù)據(jù)域?yàn)閏h */
CreateBiTree(T->lchild); /* 遞歸創(chuàng)建左子樹(shù) */
CreateBiTree(T->rchild); /* 遞歸創(chuàng)建右子樹(shù) */
}
}
3. 復(fù)制二叉樹(shù)
如果是空樹(shù),遞歸結(jié)束,否則執(zhí)行以下操作:
- 申請(qǐng)一個(gè)新的結(jié)點(diǎn)空間,復(fù)制根結(jié)點(diǎn)。
- 遞歸復(fù)制左子樹(shù)。
- 遞歸復(fù)制右子樹(shù)。
void Copy(BiTree T,BiTree &NewT)
{//復(fù)制一棵和T完全相同的二叉樹(shù)
if(T == NULL)
{
NewT = NULL;
return;
}
else
{
NewT = new BiTNode;
New->data = T->data; //復(fù)制根結(jié)點(diǎn)
Copy(T->lchild,NewT->lchild);//遞歸復(fù)制左子樹(shù)
copy(T->rchild,NewT->rchild);//遞歸復(fù)制右子樹(shù)
}
}
4. 計(jì)算二叉樹(shù)的深度
如果是空樹(shù),遞歸結(jié)束,深度為0,否則執(zhí)行以下操作:
- 遞歸計(jì)算左子樹(shù)的深度計(jì)為m。
- 遞歸計(jì)算右子樹(shù)的深度為n。
- 如果m大于n,二叉樹(shù)的深度為m+1,否則為n+1;
int Depth(BiTree T)
{
if(T == NULL) return 0;
else
{
m = Depth(T->lchild);
n = Depyh(T->rchild);
if(m>n) return(m+1);
else return (n+1);
}
}
5. 統(tǒng)計(jì)二叉樹(shù)中結(jié)點(diǎn)的個(gè)數(shù)
int NodeCount(BiTree T)
{
if(T == NULL) return 0;
else return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}
(2) 線索二叉樹(shù)
1. 線索二叉樹(shù)的基本概念
對(duì)于一個(gè)有n 個(gè)結(jié)點(diǎn)的二叉鏈表,每個(gè)結(jié)點(diǎn)有指向左右孩子的兩個(gè)指針域,所以一共是2n個(gè)指針域。而n個(gè)結(jié)點(diǎn)的二叉樹(shù)一共有n-1 條分支線數(shù),也就是說(shuō),其實(shí)是存在2n-(n-1)=n+1個(gè)空指針域。可以考慮利用那些空地址,存放指向結(jié)點(diǎn)在某種遍歷次序下的前驅(qū)和后繼結(jié)點(diǎn)的地址。這種指向前驅(qū)和后繼的指針?lè)Q為線索,加上線索的二叉鏈表稱(chēng)為線索鏈表,相應(yīng)的二叉樹(shù)就稱(chēng)為線索二叉樹(shù)。線索二叉樹(shù)等于是把一棵二叉樹(shù)轉(zhuǎn)變成一個(gè)雙向鏈表。這樣對(duì)我們插入刪除結(jié)點(diǎn)、查找某個(gè)結(jié)點(diǎn)帶來(lái)了方便。所以我們對(duì)二叉樹(shù)以某種次序遍歷使其變?yōu)榫€索二叉樹(shù)的過(guò)程稱(chēng)作是線索化。
若結(jié)點(diǎn)有左子樹(shù),則其lchild域指示其左孩子,否則令lchild指示其前驅(qū),若結(jié)點(diǎn)有右子樹(shù),則其rchild域指示其右孩子,否則令rchild域指示其后繼。為了避免混淆,需要改變結(jié)點(diǎn)結(jié)構(gòu),增加兩個(gè)標(biāo)志域,LTag、RTag。
LTag:
0->lchild域指示結(jié)點(diǎn)的左孩子
1-> lchild域指示結(jié)點(diǎn)的前驅(qū)
RTag:
0->Rchild域指示結(jié)點(diǎn)的右孩子
1-> Rchild域指示結(jié)點(diǎn)的后繼
//二叉樹(shù)的二叉線索存儲(chǔ)結(jié)構(gòu)
typedef struct BithrNode
{
TElemType data;
struct BiThrNode *lchid,*rchild;//左右孩子指針
int LTag,RTag//左右標(biāo)志
}BiThrNode,*BiThrTree;
2. 構(gòu)造線索二叉樹(shù)
由于線索二叉樹(shù)構(gòu)造的實(shí)質(zhì)是將二叉鏈表中的空指針指向前驅(qū)或后繼的線索,而前驅(qū)或后繼的信息只有在遍歷時(shí)才能得到,因此線索化的過(guò)程即為在遍歷過(guò)程中修改空指針的過(guò)程。
為了紀(jì)下遍歷過(guò)程中訪問(wèn)結(jié)點(diǎn)的先后關(guān)系,附設(shè)一個(gè)指針pre始終指向剛剛訪問(wèn)過(guò)的結(jié)點(diǎn),而指針p指向當(dāng)前訪問(wèn)的結(jié)點(diǎn),由此紀(jì)錄遍歷過(guò)程中訪問(wèn)結(jié)點(diǎn)的先后關(guān)系。
以p結(jié)點(diǎn)為根的子樹(shù)中序線索化
- 如果p非空,左子樹(shù)遞歸線索化。
- 如果p的左孩子為空,則p加上左線索,將其LTag置為1,讓p的左孩子指針指向pre(前驅(qū)),否則將p的LTag置為0。
- 如果pre的右孩子為空,則pre加上右線索,將其RTag置為1,讓pre的右孩子指向p(后繼),否則將pre的RTag置為0。
- 將pre指向剛訪問(wèn)過(guò)的結(jié)點(diǎn)p,即pre = p 。
- 右子樹(shù)遞歸線索化。
void InThreading(BiThrTree p)
{//pre是全局變量,初始化時(shí)其右孩子指針為空,便于在樹(shù)的左點(diǎn)開(kāi)始建線索。
InThreading(p->lchild); // 左子樹(shù)遞歸線索化
if(!p->lchild)//p的左孩子為空
{
p->LTag = 1;//給p加上左線索
p->lchild = pre;//p的左孩子指針指向pre(前驅(qū))
}
else p->LTag = 0;
if(!pre->rchild) //pre的右孩子為空
{
pre->RTag =1;//給pre加上右線索
pre->rchild = p;//pre的右孩子指針指向p(后繼)
}
else pre->RTag = 0;
pre = p;//保持pre指向p的前驅(qū)
InThreading(p->rchild);//右子樹(shù)遞歸線索化
}
帶頭結(jié)點(diǎn)的二叉樹(shù)中序線索化
void InOrderThreading(BiThrTree &Thrt, BiThrTree T)
{ //中序遍歷二叉樹(shù)T,并將其中序線索化,Thrt指向頭結(jié)點(diǎn)
Thrt = new BiThrNode; //建頭結(jié)點(diǎn)
Thrt->LTag = 0; //頭結(jié)點(diǎn)有左孩子,若樹(shù)非空,則其左孩子為樹(shù)根
Thrt->RTag = 1; //頭結(jié)點(diǎn)的右孩子指針為右線索
Thrt->rchild = Thrt; //初始化時(shí)右指針指向自己
if (!T) Thrt->lchild = Thrt; //若樹(shù)非空,左指針也指向自己
else
{
Thrt->lchild = T; pre = Thrt;//頭結(jié)點(diǎn)的左孩子指向根,pre初始值指向頭結(jié)點(diǎn)
InThreading(T);//以T為根的二叉樹(shù)進(jìn)行中序線索化
pre->rchild = Thrt; //結(jié)束后,pre為最右結(jié)點(diǎn),pre的右線索指向頭結(jié)點(diǎn)
pre->RTag = 1;
Thrt->rchild = pre; //頭結(jié)點(diǎn)的右線索指向pre
}
}
3. 遍歷線索二叉樹(shù)
遍歷中序線索二叉樹(shù)
- 指針p指向根結(jié)點(diǎn)
- p為非空樹(shù)或遍歷未結(jié)束,循環(huán)執(zhí)行以下操作:
- 沿左孩子向下,到達(dá)最左下結(jié)點(diǎn)*p,它是中序的第一個(gè)結(jié)點(diǎn)。
- 訪問(wèn)*p。
- 沿右線索反復(fù)查找當(dāng)前結(jié)點(diǎn)*p的后繼結(jié)點(diǎn)并訪問(wèn)后繼結(jié)點(diǎn),直至右線索為0或者遍歷結(jié)束。
- 轉(zhuǎn)向p的右子樹(shù)。
void InOrderTraverse_Thr(BiThrTree T)
{//T指向頭結(jié)點(diǎn),頭結(jié)點(diǎn)的左鏈lchild指向根結(jié)點(diǎn)。
//中序遍歷二叉線索樹(shù)T的非遞歸算法,對(duì)每個(gè)數(shù)據(jù)元素直接輸出。
BiThrTree p;
p = T->lchild; //p指向根結(jié)點(diǎn)
while (p != T) //空樹(shù)或遍歷結(jié)束,p == T
{
while (p->LTag == 0) p = p->lchild; //沿左孩子向下
cout<<p->data; //訪問(wèn)其左子樹(shù)為空的結(jié)點(diǎn)
while (p->RTag == 1 && p->rchild != T)
{
p = p->rchild;cout<<p->data;//沿右線索訪問(wèn)后繼結(jié)點(diǎn)
}
p = p->rchild; //轉(zhuǎn)向右子樹(shù)
}
}
4. 樹(shù)和森林
(1) 樹(shù)的存儲(chǔ)結(jié)構(gòu)
1. 雙親表示法
假設(shè)以一組連續(xù)空間存儲(chǔ)樹(shù)的結(jié)點(diǎn),同時(shí)在每個(gè)結(jié)點(diǎn)中,附設(shè)一個(gè)指示器指示其雙親結(jié)點(diǎn)到鏈表中的位置。也就是說(shuō),每個(gè)結(jié)點(diǎn)除了知道自己是誰(shuí)以外,還知道它的雙親在哪里。結(jié)點(diǎn)結(jié)構(gòu)為表所示:
其中data是數(shù)據(jù)域,存儲(chǔ)結(jié)點(diǎn)的數(shù)據(jù)信息。而parent是指針域,存儲(chǔ)該結(jié)點(diǎn)的雙親在數(shù)組中的下標(biāo)。
#define MAX_TREE_SIZE 100
typedef int TElemType; /*樹(shù)結(jié)點(diǎn)的數(shù)據(jù)類(lèi)型,目前暫定為整型*/
typedef struct PTNode /*結(jié)點(diǎn)結(jié)構(gòu)*/
{
TElemType data; /*結(jié)點(diǎn)數(shù)據(jù)*/
int parent; /*雙親位置*/
}PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE]; /*結(jié)點(diǎn)數(shù)組*/
int r,n; /*根的位置和結(jié)點(diǎn)數(shù)*/
}PTree
根結(jié)點(diǎn)因?yàn)闆](méi)有雙親,我們約定根結(jié)點(diǎn)的位置域設(shè)置為-1,則每個(gè)結(jié)點(diǎn)都存有其雙親的位置。
這樣的結(jié)構(gòu)容易根據(jù)結(jié)點(diǎn)的parent指針找到它的雙親結(jié)點(diǎn),時(shí)間復(fù)雜度為O(1),直到parent為-1時(shí),表示找到了樹(shù)結(jié)點(diǎn)的根。但不易找到結(jié)點(diǎn)的孩子結(jié)點(diǎn),除非遍歷整個(gè)結(jié)構(gòu),如上圖,要找到E的孩子J,必須遍歷結(jié)點(diǎn),找到parent為E的下標(biāo)4,才能找到E的孩子J。
2. 孩子表示法
由于樹(shù)中每個(gè)結(jié)點(diǎn)可能有多棵子樹(shù),可以考慮用多重鏈表,即每個(gè)結(jié)點(diǎn)有多個(gè)指針域,其中每個(gè)指針指向一棵子樹(shù)的根結(jié)點(diǎn),這種方法叫多重鏈表表示法。不過(guò),樹(shù)的每個(gè)結(jié)點(diǎn)的度,也就是它的孩子個(gè)數(shù)是不同的。 所以可以設(shè)計(jì)兩種方案來(lái)解決。
方案一:
一種是指針域的個(gè)數(shù)就等于樹(shù)的度,復(fù)習(xí)一下,樹(shù)的度是樹(shù)各個(gè)結(jié)點(diǎn)度的最大值。其結(jié)構(gòu)如圖:
其中data是數(shù)據(jù)域。child1到childd是指針域,用來(lái)指向該結(jié)點(diǎn)的孩子結(jié)點(diǎn)。上述度為3的樹(shù)若用此法表示,指針域的個(gè)數(shù)為3,如圖。
這種方法對(duì)于樹(shù)中各結(jié)點(diǎn)的度相差很大時(shí),容易浪費(fèi)空間,因?yàn)橛泻芏嗟慕Y(jié)點(diǎn),它的指針域都是空的。不過(guò)如果樹(shù)的各結(jié)點(diǎn)度相差很小時(shí),意味著開(kāi)辟的空間被充分利用了,這時(shí)存儲(chǔ)結(jié)構(gòu)的缺點(diǎn)反而變成了優(yōu)點(diǎn)。
方案二:
第二種方案每個(gè)結(jié)點(diǎn)指針域的個(gè)數(shù)等于該結(jié)點(diǎn)的度,我們專(zhuān)門(mén)取一個(gè)位置來(lái)存儲(chǔ)結(jié)點(diǎn)指針域的個(gè)數(shù),其結(jié)構(gòu)如下圖:
其中data為數(shù)據(jù)域,degree 為度域,也就是存儲(chǔ)該結(jié)點(diǎn)的孩子結(jié)點(diǎn)的個(gè)數(shù),child1到childd為指針域,指向該結(jié)點(diǎn)的各個(gè)孩子的結(jié)點(diǎn)。方案一的表示可更改如下。
這種方法克服了浪費(fèi)空間的缺點(diǎn),對(duì)空間利用率是很高了,但是由于各個(gè)結(jié)點(diǎn)的鏈表是不相同的結(jié)構(gòu),加上要維護(hù)結(jié)點(diǎn)的度的數(shù)值,在運(yùn)算上就會(huì)帶來(lái)時(shí)間上的損耗。
能否有更好的方法,既可以減少空指針的浪費(fèi)又能使結(jié)點(diǎn)結(jié)構(gòu)相同。可以用孩子表示法。具體辦法是,把每個(gè)結(jié)點(diǎn)的孩子結(jié)點(diǎn)排列起來(lái),以單鏈表作存儲(chǔ)結(jié)構(gòu),則n個(gè)結(jié)點(diǎn)有n個(gè)孩子鏈表,如果是葉子結(jié)點(diǎn)則此單鏈表為空。然后n個(gè)頭指針又組成一個(gè)線性表,采用順序存儲(chǔ)結(jié)構(gòu),存放進(jìn)一個(gè)一維數(shù)組中,如圖:
孩子鏈表
為此,設(shè)計(jì)兩種結(jié)點(diǎn)結(jié)構(gòu),一個(gè)是孩子鏈表的孩子結(jié)點(diǎn),其中child是數(shù)據(jù)域,用來(lái)存儲(chǔ)某個(gè)結(jié)點(diǎn)在表頭數(shù)組中的下標(biāo)。next是指針域,用來(lái)存儲(chǔ)指向某結(jié)點(diǎn)的下一個(gè)孩子結(jié)點(diǎn)的指針。另一個(gè)是表頭數(shù)組的表頭結(jié)點(diǎn),其中也data是數(shù)據(jù)域,存儲(chǔ)某結(jié)點(diǎn)的數(shù)據(jù)信息。firstchild是頭指針域, 存儲(chǔ)該結(jié)點(diǎn)的孩子鏈表的頭指針。
/*樹(shù)的孩子表示法結(jié)構(gòu)定義*/
#define MAX_TREE_SIZE 100
typedef struct CTNode/*孩子結(jié)點(diǎn)*/
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct/*表頭結(jié)構(gòu)*/
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct/*樹(shù)結(jié)構(gòu)*/
{
CTBox nodes[MAX_TREE_SIZE]; /*結(jié)點(diǎn)數(shù)組*/
int r, n; /*根的位置和結(jié)點(diǎn)數(shù)*/
}CTree;
此結(jié)構(gòu)便于查找某結(jié)點(diǎn)的孩子、兄弟,只需查找這個(gè)結(jié)點(diǎn)的孩子單鏈表即可。遍歷整棵樹(shù)也比較方便,對(duì)頭結(jié)點(diǎn)的數(shù)組循環(huán)即可。問(wèn)題是,不易知道某個(gè)結(jié)點(diǎn)的雙親,為此可以改進(jìn)成雙親孩子表示法。結(jié)構(gòu)如下圖:
帶雙親的孩子鏈表
3. 孩子兄弟法
對(duì)樹(shù)觀察發(fā)現(xiàn),任意一棵樹(shù), 它的結(jié)點(diǎn)的第一個(gè)孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。 因此,我們?cè)O(shè)置兩個(gè)指針, 分別指向該結(jié)點(diǎn)的第一個(gè)孩子和此結(jié)點(diǎn)的右兄弟。結(jié)構(gòu)如下圖
其中data是數(shù)據(jù)域,firstchild為指針域,存儲(chǔ)該結(jié)點(diǎn)的第一個(gè)孩子結(jié)點(diǎn)的存儲(chǔ)地址, rightsib是指針域,存儲(chǔ)該結(jié)點(diǎn)的右兄弟結(jié)點(diǎn)的存儲(chǔ)地址。結(jié)構(gòu)定義代碼如下。
/*樹(shù)的孩子兄弟表示法結(jié)構(gòu)定義*/
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode,*CSTree;
此法方便找某個(gè)結(jié)點(diǎn)的孩子,只需要通過(guò)fistchild 找到此結(jié)點(diǎn)的長(zhǎng)子,然后再通過(guò)長(zhǎng)子結(jié)點(diǎn)的rightsib 找到它的二弟,接著一直下去,直到找到具體的孩子。如果想找到某個(gè)結(jié)點(diǎn)的雙親,這個(gè)表示法就有缺陷了,可以考慮加個(gè)parent指針域來(lái)解決。其實(shí)此法最大的好處就是,把一棵復(fù)雜的樹(shù)變成了二叉樹(shù)。上圖可以變形如下:
(2) 森林與二叉樹(shù)的轉(zhuǎn)換
1. 樹(shù)轉(zhuǎn)換成二叉樹(shù)
轉(zhuǎn)化方法:
1.把所有兄弟結(jié)點(diǎn)連接起來(lái)
2.刪掉除了結(jié)點(diǎn)第一個(gè)左孩子外的連線
2. 森林轉(zhuǎn)換成二叉樹(shù)
將森林轉(zhuǎn)換成二叉樹(shù)的規(guī)則與樹(shù)類(lèi)似。先將森林中的每一棵樹(shù)轉(zhuǎn)換成二叉樹(shù),再將第一棵樹(shù)的根作為轉(zhuǎn)換后的二叉樹(shù)的根,第一棵樹(shù)的左子樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的左子樹(shù),第二棵樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的右子樹(shù),第三棵樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的右子樹(shù)的右子樹(shù),以此類(lèi)推。
3. 二叉樹(shù)轉(zhuǎn)換成森林
若二叉樹(shù)非空,則二叉樹(shù)根及其左子樹(shù)為第一棵樹(shù)的二叉樹(shù)形式,二叉樹(shù)根的右子樹(shù)可以看作是一個(gè)由除第一棵樹(shù)外的森林轉(zhuǎn)換后的二叉樹(shù),應(yīng)用同樣的方法,直到最后產(chǎn)生一顆沒(méi)有右子樹(shù)的二叉樹(shù)為止,這樣就得到了原森林。