文章目錄
1 二叉樹
2 性質
【二叉樹】是有序樹(有左右之分);一顆二叉樹的度可以小於2
【完全二叉樹】層次遍歷,中間沒有NULL結點
【滿二叉樹】
- 判斷是否爲完全二叉樹,並獲得總結點數n
- 結點的總數n和高度h關係: --> 判斷n+1是不是2的次方
性質 | 說明 |
---|---|
[推廣] 度爲m的樹 |
|
第i層上最多(i≥1)個結點 | |
高度(深度)爲k,最多有個結點 (即滿二叉樹前k層結點個數) |
|
結點編號 | |
n個結點的二叉樹一共有h(n)種可能 | |
完全二叉樹高度深度h |
2 存儲結構
存儲結構 | 圖解 | 定義 |
---|---|---|
順序存儲結構 | char BTree[]; |
|
二叉鏈表 | ||
三叉鏈表 | ||
雙親鏈表 |
2 遍歷
【遍歷】實質上是對一個非線性結構線性化操作,使每個結點(除第一個和最後一個外)在這些線性序列中有且僅有一個直接前驅和直接後繼
3 遍歷方法對比
方法對比 | 遞歸遍歷 | 非遞歸遍歷 | 線索二叉樹 |
---|---|---|---|
介紹 | 使用系統棧 | 使用用戶定義的棧 | 充分利用空鏈域(n+1個),但添加了ltag和rtag(空間的開銷對比要看具體的情況) |
對比 | 系統棧是一個所有遞歸函數都通用的棧 除了記錄訪問過的結點信息之外,還有其他信息需要記錄 |
僅保存了遍歷所需的結點信息,是專業用在二叉樹遍歷的場景中的 | 二叉樹被線索化後近似於一個線性結構,分支結構的遍歷操作就轉化爲了近似於線性結構的遍歷操作,通過線索的輔助使得尋找當前結點前驅或者後繼的平均效率大大提高 |
3 遞歸與非遞歸
-
【時間複雜度】O(n)
遍歷二叉樹的算法中的基本操作是訪問結點,則不論按哪一種次序進行遍歷,對含n個結點的二叉樹,其時間複雜度:O(n)
-
【空間複雜度】
所需輔助空間爲遍歷過程中棧的最大容量,即樹的深度,最壞情況下爲n,則空間複雜度:O(n)
【理解遍歷】將二叉樹線索化時,一個節點會遇到三次
遍歷 | 遞歸 | 非遞歸 |
---|---|---|
先序(根左右) | ||
中序(左根右 | ||
後序(左右根) | ||
層次遍歷 |
3 線索二叉樹
【線索化】對一顆二叉樹中所有結點的空指針域按照某種遍歷方式加線索的過程
【線索二叉樹】被線索化了的二叉樹爲線索二叉樹
【效率】在中序線索二叉樹上遍歷二叉樹,雖然時間複雜度亦爲O(n),但常數因子要比上節討論的算法小
【類別】
- 按遍歷順序分:先序、中序、後序
- 按lchild、rchild誰用來作爲線索:全線索化、左線索化、右線索化
typedef struct TBTNode{
char data;
int ltag,rtag; //線索標記:是否爲線索(1是,0不是)
struct TBTNode *lchild,*rchild;
}TBTNode;
中序線索二叉樹 | 前序線索二叉樹 | 後序線索二叉樹 | |
---|---|---|---|
線索化 | |||
創建 | |||
第一個 | |||
最後一個 | |||
前一個 | |||
後一個 | 1. 結點x是二叉樹的根,則其後繼爲空 2. 結點x是其爸爸的右孩子,或是其雙親的左孩子且其雙親沒有右子樹,則其後繼即爲雙親結點 3. 若結點x是其雙親的左孩子,且其雙親有右子樹,則其後繼爲雙親右子樹上按後序遍歷列出的第一個結點 |
||
遍歷 |
1 樹
2 性質
【樹的分類】有序樹;無序樹
【豐滿樹】即理想平衡樹,要求除最底層外,其他層都是滿的(完全二叉樹的擴展)
【性質】度爲m的樹:
2 存儲結構
存儲結構 | 圖解 | 定義 | 評價 |
---|---|---|---|
雙親表示法 | 找雙親O(1);找根O(h);找孩子O(n) | ||
孩子表示法 | |||
雙親孩子表示法 | |||
孩子兄弟表示法(二叉鏈表) | 父親牽老大;老大左手牽第一個孩子,右手牽自己的兄弟 |
2 樹和森林遍歷問題
樹 | 森林 | 用二叉鏈表表示法存儲時,對應其二叉樹的遍歷方式 |
---|---|---|
先根遍歷 | 先序遍歷 | 先序遍歷 |
後根遍歷 | 中序遍歷 | 後序遍歷 | 中序遍歷 |
【樹的中序遍歷問題】第二次經過該結點時進行訪問
1 哈夫曼樹(最優樹)
【哈夫曼樹】WPL最小的樹稱爲哈夫曼樹或最優樹(哈夫曼樹就是最優樹)
2 相關概念
概念 | 說明 | 舉例 |
---|---|---|
路徑 | 兩結點的路 | [60->5] 60->28->11->5 |
路徑長度 | 兩結點之間線的數量 | [60->5] 3條 |
樹的路徑長度 | 【根】到每個結點的線之和 | |
帶權路徑長度WPL | 結點到【根】線數量*權重 | [5] 4*5=20 |
樹的帶權路徑長度WPL | 葉子結點的WPL之和 | 19*2 + 21*2 + 32*2 + 6*4 + 7*4 + 10*4 + 2*5 + 3*5 = =(19+21+32)*2 + (6+7+10)*4 + (2+3)*5 |
2 哈夫曼二叉樹
3 特點
特點 | 說明 |
---|---|
【重點】哈夫曼二叉樹中沒有,只有和;這類樹又叫正則(嚴格)二叉樹 | 【推廣】哈夫曼m叉中只有和的結點 【就有公式】 |
個葉子結點的赫夫曼樹共有個結點 | |
權值越大的結點,距離根結點越近 | |
樹的帶權路徑長度最短 | |
由同一組結點得到的哈夫曼樹可能不唯一 | 【兩種情況】①結點有相同的權重–>【注意】爲避免評卷老師誤判,一般從左到右選; ②左右子樹位置互換 --> 【注意】爲避免評卷老師誤判,做題時儘量保證: 左子樹根權重<右子樹根權重 |
3 哈夫曼編碼
【等長碼】權重相同時,效率最高;定長使得翻譯很方便
問題 | 解決 | 哈夫曼編碼產生的是最短前綴碼 |
---|---|---|
不定長編碼的解碼結果不唯一(二義性) | 【前綴碼】任一字符的編碼串都不是另一個字符編碼串的前綴–>解碼結果唯一 | 被編碼的字符都處於葉子結點上,而根通往任一葉子結點的路徑都不可能是通往其餘葉子結點路徑的子路徑 --> 任一編碼串不可能是其他編碼串的子串 |
所有的二叉樹都是前綴碼 | 哈夫曼樹的帶權路徑長度是最短的 | 每個字符的權值是在字符串中出現的次數,路徑長度即每個字符編碼的長度,出現次數越多的字符編碼長度越短 --> 這使得整個字符串被編碼後的前綴碼長度最短 |
3 代碼
typedef struct node{
int w;
struct node *lchild,*rchild;
}BiTNode, *BiTree;
BiTree CreateHufferman(int num[], int n) {
BiTree *tmp,p;
int k1,k2;
int i,j;
//創建一個BiTree數組來存結點
tmp = (BiTree *)malloc(sizeof(BiTree) * n);
for (i=0; i<n; i++) {
//創建新的結點
tmp[i] = (BiTNode *)malloc(sizeof(BiTNode)); if (!tmp[i]) exit(0);
tmp[i]->w = num[i];
tmp[i]->lchild = tmp[i]->rchild = NULL;
}
for (i=1; i<n; i++) { //循環n-1次,構造哈夫曼樹
//選出前兩個
k1 = -1;
for (j=0; j<n; j++) {
if (tmp[j]!=NULL && k1==-1) { //找到了第一個
k1 = j;
continue; //找k2
}
if (tmp[j]!=NULL) { //找到了第二個
k2 = j;
break; //退出
}
}
//找出最小的、次小的
for (j=k2; j<n; j++) {
if (tmp[j]!=NULL) {
if (tmp[j]->w < tmp[k1]->w) { //j < k1
k2 = k1; //△
k1 = j;
} else if (tmp[j]->w < tmp[k2]->w) { //k1 < j < k2
k2 = j;
}
}
}
//合併k1,k2
p = (BiTNode *)malloc(sizeof(BiTNode)); if (!p) exit(0);
p->w = tmp[k1]->w + tmp[k2]->w;
p->lchild = tmp[k1];
p->rchild = tmp[k2];
tmp[k1] = p; // 新結點的放到k1的位置
tmp[k2] = NULL; // k2位置爲空
}
free(tmp); //刪除動態建立的數組tmp
return p; //返回哈夫曼樹的根結點
}
int GetWPL(BiTree T, int pathLen) {
// 葉子結點
if (T->lchild==NULL && T->rchild==NULL) return T->w * pathLen;
return GetWPL(T->lchild, pathLen+1) + GetWPL(T->rchild, pathLen+1);
}
void GetCode(BiTree T, int pathLen) {
static char tmp[2*MAX-1];
int i;
if (T->lchild==NULL && T->rchild==NULL) {
printf("\n%d\t", T->w);
for (i=0; i<pathLen; i++) {
printf("%c", tmp[i]);
}
return ;
}
//往左走
tmp[pathLen] = '0';
GetCode(T->lchild, pathLen+1);
//往右走
tmp[pathLen] = '1';
GetCode(T->rchild, pathLen+1);
}
2 哈夫曼n叉樹
【哈夫曼n叉樹】每次選n個最優的來構造新的結點
【舉例】哈夫曼三叉樹:每次選3個最優解來構造新的結點
【舉例】結點數不夠時,用0來補充 --> WPL依舊是最小
1 例題
問題 | 解決 |
---|---|
是否爲完全二叉樹 | 層次遍歷,中間沒有NULL結點 |
是否爲滿二叉樹 | 1. 判斷是否爲完全二叉樹,並獲得總結點數n 2. 結點的總數n和高度h關係: --> 判斷n+1是不是2的次方 |
先序、中序序列確定二叉樹 | 1. 先從先序獲得根 2. 帶着根去中序中確定左右子樹 |
二叉樹葉子結點的個數 | |
二叉樹的深度 | |
複製二叉樹 | |
先序序列創建二叉樹 | |
根到葉子結點的所有路徑 | |
中綴表達式建立二叉樹 | |
求樹的深度(二叉鏈表表示) | depth=max(h1+1, h2);左子樹:是下一級的深度;右子樹:是同一級的深度 |
輸出樹中所有從根到葉子的路徑 | 左子樹根到葉子結點的路徑 |
由二元組建樹存儲結構 | 博客 |
若度爲m的哈夫曼樹,葉子結點個數爲n,則非葉子結點的個數爲⌈(n-1)/(m-1)⌉ |
1. 葉子結點個數n,即爲構造時的初始結點 2. 考慮構造過程,根結點的個數初始爲n個 3. 第一次合併,m個結點合成一個結點,根結點減少了m-1個,根結點只剩下n-m+1個 4. 第二次合併,m個結點合成一個結點,根結點繼續減少了m-1個,根結點只剩下n-2*(m-1) 5. 第k次合併,m個結點合成一個結點,根結點繼續減少m-1個,根結點只剩下1個 根據以上,可以得到n-k*(m-1)=1 --> k=⌈(n-1)/(m-1)⌉。而k正是非葉子結點的個數,即每次構造新生成的結點 |