數據結構二叉樹相關概念複習

                      樹(一對多的數據結構)

樹(Tree)是n(n>=0)個結點的有限集。n=0時稱爲空樹。在任意一顆非空樹種:
(1)有且僅有一個特定的稱爲根(Root)的結點;
(2)當n>1時,其餘結點可分爲m(m>0)個互不相交的有限集T1、T2、......、Tn,其中每一個集合本身又是一棵樹,並且稱爲根的子樹。

對於樹的定義還需要強調兩點:
1.n>0時根結點是唯一的,不可能存在多個根結點,數據結構中的樹只能有一個根結點。
2.m>0時,子樹的個數沒有限制,但它們一定是互不相交的。

結點分類:
結點擁有的子樹數稱爲結點的度。度爲0的結點稱爲葉結點或終端結點;度不爲0的結點稱爲非終端結點或分支結點。除根結點之外,分支結點也稱爲內部結點。樹的度是樹內各結點的度的最大值。

結點間關係:
結點的子樹的跟稱爲該結點的孩子,相應地,該結點稱爲孩子的雙親。
同一個雙親的孩子之間互稱兄弟,結點的祖先是從根到該結點所經分支上的所有結點。

樹的其他相關概念:
結點的層次從根開始定義起,根爲第一層,根的孩子爲第二層。若某結點在第I層,則其子樹的根就在第I+1層。其雙親在同一層的結點互爲堂兄弟。
樹中結點的最大層次稱爲樹的深度或高度。
如果將樹種結點的各子樹看成從左至右是有次序的,不能互換的,則稱該樹爲有序樹,否則稱爲無序樹。
森林是m(m>=0)課互不相交的樹的集合。

 

樹的存儲結構:
雙親表示法、孩子表示法、孩子兄弟表示法。

1.雙親表示法(時間複雜度爲O(1)):
在每個結點中,附設一個指示器指示其雙親結點到鏈表中的位置。
結點結構爲:data | parent
其中data是數據域,存儲結點的數據信息。而parent是指針域,存儲該結點的雙親在數組中的下標。
由於根結點是沒有雙親的,所以我們約定根結點的位置域設置爲-1.

2.孩子表示法:
把每個結點的孩子結點排列起來,以單鏈表作存儲結構,則n個結點有n個孩子鏈表,如果是葉子結點則此單鏈表爲空,然後n個頭指針又組成一個線性表,採用順序存儲結構,存放進一個一維數組中。

爲此,設計兩種結點結構:
一個是孩子鏈表的孩子結點, child | next
其中child是數據域,用來存儲某個結點在表頭數組中的下標。next是指針域,用來存儲指向某結點的下一個孩子結點的指針。
另一個是表頭數組的表頭結點, data | firstchild
其中data是數據域,存儲某結點的數據信息。firstchild是頭指針域,存儲該結點的孩子鏈表的頭指針。


3.孩子兄弟表示法:
任意一棵樹,它的結點的第一個孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我們設置兩個指針,分別指向該結點的第一個孩子和此結點的右兄弟。
結點結構如表所示:
data | firstchild | rightsib 
其中data是數據域,first child爲指針域,存儲該結點的第一個孩子結點的存儲地址,rightsib是指針域,存儲該結點的右兄弟結點的存儲地址。

 

                              二叉樹

二叉樹的定義:二叉樹是n(n>=0)個結點的有限集合,該集合或者爲空集(稱爲空二叉樹),或者由一個根結點和兩棵互不相交的、分別稱爲根結點的左子樹和右子樹組成。(在某個階段都是兩種結果的情形)

二叉樹的特點有:
*每個結點最多有兩顆子樹,所以二叉樹中不存在度大於2的結點。
*左子樹和右子樹是有順序的,次序不能任意顛倒。
*即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。

二叉樹具有五種基本形態:
1.空二叉樹。
2.只有一個根結點。
3.根結點只有左子樹。
4.根結點只有右子樹。
5.根結點既有左子樹又有右子樹。

 

特殊二叉樹:
1.斜樹:所有的結點都只有左子樹的二叉樹叫左斜樹。所有結點都是隻有右子樹的二叉樹叫右斜樹。這兩者統稱爲斜樹。

2.滿二叉樹:在一棵二叉樹中。如果所有分支結點都存在左子樹和右子樹,並且所有葉子都在同一層上,這樣的二叉樹稱爲滿二叉樹。
滿二叉樹的特點有:
*葉子只能出現在嘴下一層。出現在其它層就不可能達成平衡。
*非葉子結點的度一定是2。
*在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最多。

3.完全二叉樹:對一顆具有n個結點的二叉樹按層編號,如果編號爲i(1<=i<=n)的結點與同樣深度的滿二叉樹中編號爲i的結點在二叉樹中位置完全相同,則這棵二叉樹稱爲完全二叉樹。
完全二叉樹的特點:
*滿二叉樹一定是一棵完全二叉樹,但完全二叉樹不一定是滿的。


*葉子結點只能出現在最下兩層。
*最下層的葉子一定集中在左部連續位置。
*倒數二層,若有葉子結點,一定都在右部連續位置。
*如果結點度爲1,則該結點只有左孩子,即不存在只有右子樹的情況。
*同樣結點的二叉樹,完全二叉樹的深度最小。

**判斷某二叉樹是否是完全二叉樹:
給每個結點按照二叉樹的結構逐層順序編號,如果編號出現空擋,就說明不是完全二叉樹,否則就是。

 

二叉樹的性質
1.性質1:在二叉樹的第i層上至多有2∧i-1個結點(i>=1)。
2.性質2:深度爲k的二叉樹至多有2∧k -1個結點(k>=1)。
3.性質3:對任何一棵二叉樹T,如果其終端結點數爲n0,度爲2的結點數爲n2,則n0=n2+1。
4.性質4:具有n個結點的完全二叉樹的深度爲[log2n]+1 ([x]表示不大於x的最大整數。
5.性質5:如果對一棵有n個結點的完全二叉樹(其深度爲[log2n]+1) 的結點按層序編號(從第1層到[log2n]+1層,每層從左到右),對任一節點i(1≦i≦n)有:
*.如果i=1,則結點i是二叉樹的根,無雙親;如果i>1, 則其雙親是結點[i/2]。
*.如果2i>n, 則結點i無左孩子(結點i爲葉子結點);否則其左孩子是結點2i。
*.如果2i+1>n, 則結點i無右孩子;否則其右孩子是結點2i+1。

 

##二叉樹的存儲結構

1.二叉樹的順序存儲結構:
二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點,並且結點的存儲位置,也就是數組的下標要能體現結點之間的邏輯關係。
*順序存儲結構一般只用於完全二叉樹。

2.二叉鏈表(鏈式存儲結構)
二叉樹每個結點最多有兩個孩子,所以爲它設計一個數據域和兩個指針域是比較自然的想法,我們稱這樣的鏈表叫做二叉鏈表。

 

##二叉樹的遍歷:是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點唄訪問一次且僅被訪問一次。

二叉樹遍歷方法
1.前序遍歷:規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。

2.中序遍歷:規則是若樹爲空,則空操作返回,否則從根結點開始(注意並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。

3.後序遍歷:規則是若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。

4.層序遍歷:規則是若樹爲空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。

*前序遍歷算法:
/*二叉樹的前序遍歷遞歸算法*/
void PreOrderTraverse(BiTree T)
{
if(T==NULL)
return;
printf("%c", T-?lchild); /*顯示結點數據,可以更改爲其他對結點操作*/
PreOrderTraverse(T->lchild); /*再先序遍歷左子樹*/
PreOrderTraverse(T->rchild); /*最後先序遍歷右子樹*/
}

*中序遍歷算法:
/*二叉樹的中序遍歷遞歸算法*/
void InOrderTraverse(BiTree T)
{
if(T==NULL)
return;
InOrderTraverse(T->lchild); /*中序遍歷左子樹*/
printf("%c", T->data); /*顯示結點數據,可以更改爲其他對結點操作*/
InOrderTraverse(T->rchild); /*最後中序遍歷右子樹*/
}

*後序遍歷算法:
/*二叉樹的後序遍歷遞歸算法*/
void PostOrderTraverse(BiTree T)
{
if(T==NULL)
return;
PostOrderTraverse(T->lchild); /*先後序遍歷左子樹*/
PostOrderTraverse(T->rchild); /*再後續遍歷右子樹*/
printf("%c", T->data); /*顯示結點數據,可以更改爲其他對結點操作*/
}

**已知前序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹。
已知後序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹。

 

##二叉樹的建立:建立二叉樹,也是利用了遞歸的原理。只不過在原來應該是打印結點的地方,改成了生成結點,給結點賦值的操作而已。
**對二叉樹進行拓展:將二叉樹中每個結點的空指針引出一個虛節點,其值唯一特定值,比如”#“。

用前序遍歷生成二叉樹:
/*按前序輸入二叉樹中結點的值(一個字符)*/
/** #表示空樹,構造二叉鏈表表示二叉樹T。*/
void CreateBiTree(BiTree *T)
{
TElemType ch;
scanf("%c", &ch);
if(ch=='#')
*T=NULL;
else
{
*T=(BiTree)malloc(sizeof(BiTNode));
if(!*T)
exit(OVERFLOW);
(*T)->data=ch; /*生成根結點*/
CreateBiTree(&(*T)->lchild); /*構造左子樹*/
CreateBiTree(&(*T)->rchild); /*構造右子樹*/
}
}

 

##線索二叉樹

*對於一個有n個結點的兒茶鏈表,每個結點有指向左右孩子的兩個指針域,所以一共是2n個指針域。而n個結點的二叉樹一共有n-1條分支線數,也就是說,其實是存在2n-1(n-1)=n+1個空指針域。

線索二叉樹:指向前驅和後繼的指針稱爲線索,加上線索的二叉鏈表稱爲線索鏈表,相應的二叉樹就稱爲線索二叉樹。

*線索二叉樹,等於是把一棵二叉樹轉變成了一個雙向鏈表。

*對二叉樹以某種次序遍歷使其變爲線索二叉樹的過程稱作是線索化。

#線索二叉樹結構實現:
/*二叉樹的二叉線索存儲結構定義*/
typedef enum(Link,Thread) PointerTag; /*Link==0表示指向左右孩子指針*/
/*Thread==1表示指向前驅或後繼的線索*/
typedef struct BiThrNode /*二叉樹線索存儲結點結構*/
{
TElemType data; /*結點數據*/
struct BiThrNode *lchild, *rchild; /*左右孩子指針*/
PointerTag LTag;
PointerTag RTag; /*左右標誌*/
}BiThrNode, *BiThree;

*線索化的實質就是將二叉鏈表中的空指針改爲指向前驅或後繼的線索。由於前驅和後繼的信息只有在遍歷該二叉樹時才能得到,所以線索化的過程就是在遍歷的過程中修改空指針的過程。

*線索二叉樹的時間複雜度爲O(n).

#如果所用的二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和後繼,那麼採用線索二叉鏈表的存儲結構就是非常不錯的選擇。

 

##樹、森林與二叉樹的轉換

#.樹轉換爲二叉樹
1.加線。在所有兄弟結點之間加一條連線。
2.去線。對樹中每個結點,只保留它與第一個孩子結點的連線,刪除它與其他孩子結點之間的連線。
3.層次調整。以樹的根結點爲軸心,將整棵樹順時針旋轉一定的角度,使之結構層次分明。注意第一個孩子是二叉樹結點的左孩子,兄弟轉換過來的孩子是結點的右孩子。

#森林轉換爲二叉樹
1.把每個樹轉換爲二叉樹。
2.第一棵二叉樹不動,從第二棵二叉樹開始,依次把後一棵二叉樹的根結點作爲前一棵二叉樹的根結點的右孩子,用線連接起來。當所有的二叉樹連接起來後就得到了由森林轉換來的二叉樹。

#二叉樹轉換爲樹
1.加線。若某結點的右孩子存在,則將做左孩子的n各右孩子結點都作爲此結點的孩子。將該結點與這些右孩子結點用線連接起來。
2.去線。刪除原二叉樹中所有結點與其右孩子結點的連線。
3.層次調整。使之結構層次分明。

***判斷一棵二叉樹能夠轉換成一棵樹還是森林,就是隻要看這棵二叉樹的根結點有沒有右孩子,有就是森林,沒有就是一棵樹。

#二叉樹轉換爲森林
1.從根結點開始,若右孩子存在,則把與右孩子結點的連線刪除,在查看分離後的二叉樹,若右孩子存在,則連線刪除......,直到所有右孩子連線都刪除爲止,得到分離的二叉樹。
2.再將每棵分離後的二叉樹轉換爲樹即可。

 

 

樹與森林的遍歷
樹的遍歷分爲兩種方式
1.一種是先根遍歷樹,即先訪問樹的根結點,然後依次先根遍歷根的每棵子樹。、
2.另一種是後跟遍歷,即先依次後根遍歷每棵子樹,然後再訪問根結點。

 

森林的遍歷也分爲兩種方式:
1.前序遍歷:先訪問森林中第一棵樹的根結點,然後再依次縣根遍歷根的每棵子樹,再依次用同樣方式遍歷除去第一棵樹的剩餘樹構成的森林。
2.後序遍歷:是先訪問森林中第一棵樹,後跟遍歷的方式遍歷每棵子樹,然後再訪問根結點,再依次同樣方式遍歷除去第一棵樹的剩餘樹構成的森林。

 

**當以二叉樹做作樹的存儲結構時,樹的先根遍歷和後跟遍歷完全可以借用二叉樹的前序遍歷和中序遍歷的算法來實現。

 

 

赫夫曼樹及其應用
1、路徑和路徑長度
  在一棵樹中,從一個結點往下可以達到的孩子或子孫結點之間的通路,稱爲路徑。通路中分支的數目稱爲路徑長度。若規定根結點的層數爲1,則從根結點到第L層結點的路徑長度爲L-1。
2、結點的權及帶權路徑長度
  若將樹中結點賦給一個有着某種含義的數值,則這個數值稱爲該結點的權。結點的帶權路徑長度爲:從根結點到該結點之間的路徑長度與該結點的權的乘積。
3、樹的帶權路徑長度
  樹的帶權路徑長度規定爲所有葉子結點的帶權路徑長度之和,記爲WPL。
其中帶權路徑長度WPL最小的二叉樹稱作赫夫曼樹。

 

#赫夫曼樹的構造:
1.先把有權值得葉子結點按照從小到大的順序排列成一個有序序列,即:A5, E10, B15, D30, C40。
2.取頭兩個最小權值的結點作爲一個新節點N1的兩個子結點,注意相對較小的是左孩子,這裏就是A爲N1的左孩子,E爲N1的右孩子。新結點的權值爲兩個葉子權值得和5+10=15.
3.將N1替換A與E,插入有序序列中,保持從小到大排列。即:N115,B15,D30,C40.
4.重複步驟2.將N1與B作爲一個新節點N2的兩個子結點。N2的權值=15+15=30。
5.將N2替換N1與B,插入有序序列中,保持從小到大排列。即:N230,D30,C40.
6.重複步驟2.將N2於D作爲一個新節點N3的兩個子結點。N3的權值=30+30=60.
7.將N3替換N2與D,插入有序序列中,保持從小到大排列。即:C40, N360.
8.重複步驟2.將C與N3作爲一個新節點T的兩個子結點,由於T即是根結點,完成赫夫曼樹的構造。

 

#構造赫夫曼樹的赫夫曼算法描述:
1.根據給定的n個權值{w1,w2,…,wn}構成n棵二叉樹的集合F={T1,T2,…,Tn},其中沒棵二叉樹Ti中只有一個帶權爲w1根結點,其左右子樹均爲空。
2.在F中選取兩棵根結點的權值最小的樹作爲左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值爲其左右子樹上根結點的權值之和。
3.在F中刪除這兩棵樹,同時將新得到的二叉樹加入F中。
4.重複2和3步驟,直到F只含一棵樹爲止。這棵樹便是赫夫曼樹。

 

#赫夫曼編碼
*若要設計長短不等的編碼,則必須是任一字符的編碼都不是另一個字符的編碼的前綴,這種編碼稱作前綴編碼。

 

*一般地,設需要編碼的字符集爲{d1,d2,…,dn},各個字符在電文中出現的次數或頻率集合爲{w1,w2,…,wn},以d1,d2,…,dn作爲葉子結點,以w1,w2,…,wn作爲相應葉子結點的權值來構造一棵赫夫曼樹。規定赫夫曼樹的左分支代表0,右分支代表1,則從根結點到葉子結點所經過的路徑分支組成的0和1的序列便爲該結點對應字符的編碼,這就是赫夫曼編碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章