6.2 樹的定義
- 是n(n>=0)個結點的有限集,1.n=0爲空樹;2.有且僅有一個結點稱爲根(Root)結點;3.當n>1時,其餘結點可分爲m(m>0)個互不相交的有限集合,根的子樹。
- 根結點、內部結點、葉結點或終端結點
- 樹中結點的最大層次稱爲樹的深度或高度
6.2.1 結點分類
- 結點擁有的子樹數稱爲結點的度
- 度爲0稱爲葉結點或終端結點
- 度不爲0,非終端結點或分支結點(內部結點)
- 樹的度是樹內各結點點的最大值
6.2.2 結點間關係
- 結點子樹稱做孩子結點,改結點稱做孩子的雙親結點。
- 兄弟結點。
- 樹中結點最大層次稱爲樹的深度或高度
- 森林是 m 棵互不相交的數的集合
6.3 數的抽象數據結構
6.4 樹的存儲結構
6.4.1 雙親表示法
- data(數據域) 和 parent(指針域,雙親在數組中的下標)
- 改進:data、parent、firstchild(長子域)
- 雙親域、長子域、右兄弟域
typedef int TElemType;
typedef struct PTNode{
TElemeType data; //結點數據
int parent; //雙親位置
}PTNode;
typedef struct{
PTNode nodes[MAXSIZE];
int r; //根結點
int n; //結點數
}
6.4.2 孩子表示法
- 每個結點有多個指針域,其中每個指針指向一個子樹的根節點,叫多重鏈表表示法。
- 兩種方式:
1.data child1 child2
指針域的個數等於樹的度
2.data degree(度域) child1 child2
每個結點指針域的個數等於該結點的度,一個位置存儲結點指針域的個數
- 孩子表示法:把每個結點的孩子結點排列起來,以單鏈表作爲存儲結構,則n個結點有n個孩子鏈表,如果是葉子節點則此單鏈表爲空,然後 n 個頭指針又組成一個線性表,採用順序存儲結構,存放到一維數組中。
//孩子結點
typedef struct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
//表頭
typedef struct {
TElemType data;
ChildPtr firstchild;
}CTBox;
//樹結構
typedef struct{
CTBox nodes[MAXSIZE];
int r; //根結點
int n; //結點數;
}
- 孩子雙親表示法
6.4.3 孩子兄弟表示法
- 任意一棵樹, 它的結點的第一個孩子如果存在就是唯一的,它的兄弟如果存在也是唯一的, 因此,我們設置兩個指針 分別指向該結點的第一個孩子和此結點的右兄弟。
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild, *rightsib;
} CSNode,*CSTree;
找雙親結點域缺陷,增加一個 parent 指針域。
6.5 二叉樹
- 二叉樹是n(n>=0)個結點的有限集合,該集合或者爲空集(稱爲空二叉樹),或者由一個根結點和兩棵互不相交的、分別稱爲根結點的左子樹和右子樹的二叉樹組成。
6.5.1 二叉樹特點
- 五種形態
1.空二叉樹
2.只要一個根結點
3.根結點只有左子樹
4.根結點只有右子樹
5.根結點既有左子樹,又有右子樹。
6.5.2 特殊二叉樹
- 斜樹:左斜樹、右斜樹
- 滿二叉樹:除了葉子結點(必須在同一層上),所有結點都有左右子樹。
- 完全二叉樹
6.6 二叉樹的性質
- 在二叉樹的第 i 層上至多有 2i-1個結點
- 深度爲 k 的二叉樹至多有 2k-1 個結點
- 對任何一棵二叉樹 T,如果其終端結點數爲 n0,度爲 2 的結點數爲n2,則 n0=n2+1
- 具有n個結點的完全二叉樹的深度爲 [log2n] + 1
- 對於一棵有n個結點的完全二叉樹的結點按層序標號,對任一結點i(i<=i<=n)有:1.如果i=1 ,則結點i是完全二叉樹的根,無雙親;如果i>1,則雙親是結點[i/2]。2.如果2i>n ,則結點 i 無左孩子(結點 i 爲葉子結點) ;否則其左孩子是結點2i;3.如果 2i+1>n ,則結點i無右孩子;否則其右孩子是結點 2i+1
6.7 二叉樹的存儲結構
- 順序存儲結構,一般只用於完全二叉樹。
6.7.2.二叉鏈表
- 二叉樹每個結點最多有兩個孩子,數據域和兩個指針域。二叉鏈表
- 結構:lchild data rchild
typedef struct BiTNode{
TElemeType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
6.8. 遍歷二叉樹
- 從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。(訪問、次序)
6.8.2 二叉樹遍歷方法
- 前序遍歷 根 左 右
- 中序遍歷 左 根 右
- 後序遍歷 左 右 根
- 層序遍歷 一層 自上而下
A
B C
D E F
G H I
前序:ABDGHCEIF
中序:GDHBAEICF
後序:GHDBIEFCA
層序:ABCDEFGHI
//前序 根左右
void PreOrderTraverse(BiTree T){
if(T == null){
return;
}
printf("%c",T->data);
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);
}
5.已知前序和中序(後序和中序)可以確定唯一二叉樹
6.9 二叉樹的建立
- 擴展二叉樹
void CreateBiTree(BiTree *T){
TElemType ch;
scanf("%c",&ch);
if(ch == "#"){
*T = NULL;
}else{
*T = (BiTree)malloc(sizeof(BiTNOde));
if(!*T){
exit(OVERFLOW);
}else{
(*T)->data = ch;
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
}
6.10 線索二叉樹
- 二叉樹
- 指向前驅和後驅的指針稱爲線索,加上線索的二叉鏈表稱爲線索二叉樹,相應的二叉樹稱爲線索二叉樹。
- 下圖中序遍歷:將 rchild 指向下一個結點。
所有空指針域 lchild 指向前驅。
- 對二叉樹以某種次序遍歷使其變爲線索二叉樹的過程稱做是線索化。
- 區分 lchild 指向左孩子還是前驅結點,需要加 tag 判斷
//二叉樹的二叉線索樹存儲結構定義
typedef enum {
Link,Thread
}PointerTag;//Link=0 表示指向左右孩子指針,Thread = 1 表示指向前驅或後驅
typedef struct BiThrNode{
TElemType data;
struct BiThreNode *lchild,*rchild;
PointerTag LTag;
PointerTag RTag;
}BiTheNode,*BiTheTree;
- 線索化實質,將二叉鏈表中的空指針改爲指向前驅或後驅的線索,線索化的過程就是在遍歷的過程中修改空指針的過程。
BiThrThree pre;
void InTheading(BiThrThree p){
if(p){
InThreadDing(p->lchild);//遞歸左子樹線索化
if(!p->lchild){ //沒有左孩子
p->LTag = Thread; //前驅線索
p->lchild = pre; //指向前驅
}
if(!pre->rchild){ //前驅沒有右孩子
pre->RTag = Thread;
pre->rchild = p; //前驅右孩子指針指向後驅(當前節點 p)
}
pre = p; // 保持 pre 指向 p 的前驅
InThreading(p->rchild); //遞歸右子樹線索化
}
}
//遍歷
Status InOrderTraverse_Thr(BiThrTree T){
BiThrTree p;
p = T->lchild; //p 指向根節點
while(p != T){ //空樹或遍歷結束,p == T
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; //p進至其右子樹根
}
return OK;
}
- 如果所用的二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和後繼,那麼採用線索二叉鏈袤的存儲結構就是非常不錯的選擇。
6.11 樹、森林與二叉樹的轉換
6.11.1 樹轉換爲二叉樹
- 加線,兄弟結點加線
- 去線,保留第一個孩子線
- 層次調整,順時針選擇一定角度
6.11.2 森林轉換爲二叉樹
- 把每棵樹轉換成二叉樹
- 第一棵不動,後面的樹把根極點作爲前一棵根結點孩子。
6.11.3 二叉樹轉換爲樹
6.11.4 二叉樹轉換爲森林
6.11.5 樹與森林的遍歷
- 樹遍歷:先根遍歷、後根遍歷
- 森林遍歷:前序、後序
6.12 赫夫曼樹及應用
6.12.1 赫夫曼樹
- 從樹中一個結點到另一個結點之間的分支構成兩個結點之間的路,路徑上的分支數目稱做路勁長度。
- 樹的路徑長度就是從樹根到每一結點的路徑長度之和
- 帶權路徑長度 WPL 最小的二叉樹稱做赫夫曼樹,也叫最優二叉樹。
6.12.3 赫夫曼編碼
- 若要設計長短不等的編碼,則必須是任一字符的編碼都不是另一個字符的編碼的前綴,這種編碼稱做前綴編碼。