哈夫曼樹算法
哈夫曼樹也叫最優二叉樹(哈夫曼樹) 數組
問題:什麼是哈夫曼樹?編碼
例:將學生的百分制成績轉換爲五分製成績:≥90 分: A,80~89分: B,70~79分: C,60~69分: D,<60分: E。spa
if (a < 60){
b = 'E'; } else if (a < 70) { b = ‘D’; } else if (a<80) { b = ‘C’; } else if (a<90){ b = ‘B’; } else { b = ‘A’; }
判別樹:用於描述分類過程的二叉樹。設計
若是每次輸入量都很大,那麼應該考慮程序運行的時間3d
若是學生的總成績數據有10000條,則5%的數據需 1 次比較,15%的數據需 2 次比較,40%的數據需 3 次比較,40%的數據需 4 次比較,所以 10000 個數據比較的指針
次數爲: 10000 (5%+2×15%+3×40%+4×40%)=31500次code
此種形狀的二叉樹,須要的比較次數是:10000 (3×20%+2×80%)=22000次,顯然:兩種判別樹的效率是不同的。blog
問題:能不能找到一種效率最高的判別樹呢? element
那就是哈夫曼樹
回憶樹的基本概念和術語
例:有4 個結點 a, b, c, d,權值分別爲 7, 5, 2, 4,試構造以此 4 個結點爲葉子結點的二叉樹。
WPL=7´2+5´2+2´2+4´2= 36
WPL=7´3+5´3+2´1+4´2= 46
WPL=7´1+5´2+2´3+4´3= 35
WPL=7´1+5´2+2´3+4´3= 35
後二者其實就是最有二叉樹(也就是哈夫曼樹)
例:有4 個結點 a, b, c, d,權值分別爲 7, 5, 2, 4,構造哈夫曼樹。
根據給定的n個權值{w1,w2,…,wn}構成二叉樹集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權爲wi的根結點,其左右子樹爲空.
在F中選取兩棵根結點權值最小的樹做爲左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值爲左右子樹根結點的權值之和.
在F中刪除這兩棵樹,同時將新的二叉樹加入F中.
重複,直到F只含有一棵樹爲止.(獲得哈夫曼樹)
關於哈夫曼樹的注意點:
一、滿二叉樹不必定是哈夫曼樹
二、哈夫曼樹中權越大的葉子離根越近 (很好理解,WPL最小的二叉樹)
三、具備相同帶權結點的哈夫曼樹不唯一
四、哈夫曼樹的結點的度數爲 0 或 2, 沒有度爲 1 的結點。
五、包含 n 個葉子結點的哈夫曼樹中共有 2n – 1 個結點。
六、包含 n 棵樹的森林要通過 n–1 次合併才能造成哈夫曼樹,共產生 n–1 個新結點
根據給定的n個權值{w1,w2,…,wn}構成二叉樹集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權爲wi的根結點,其左右子樹爲空.
在F中選取兩棵根結點權值最小的樹
做爲左右子樹構造一棵新的二叉樹,置新的二叉樹的根結點的權值爲左右子樹根結點的權值之和
在F中刪除這兩棵樹,同時將新的二叉樹加入F中.
重複,直到F只含有一棵樹爲止.(獲得哈夫曼樹)
在F中刪除這兩棵樹,同時將新的二叉樹加入F中.
構造完畢(哈夫曼樹,最有二叉樹),也就是最佳斷定樹
哈夫曼編碼
哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通訊中的應用之一。普遍地用於數據文件壓縮的十分有效的編碼方法。其壓縮率一般在20%~90%之間。在電訊通訊業務中,一般用二進制編碼來表示字母或其餘字符,並用這樣的編碼來表示字符序列。
例:若是需傳送的電文爲 ‘ABACCDA’,它只用到四種字符,用兩位二進制編碼即可分辨。假設 A, B, C, D 的編碼分別爲 00, 01,10, 11,則上述電文便爲 ‘00010010101100’(共 14 位),譯碼員按兩位進行分組譯碼,即可恢復原來的電文。
可否使編碼總長度更短呢?
實際應用中各字符的出現頻度不相同,用短(長)編碼表示頻率大(小)的字符,使得編碼序列的總長度最小,使所需總空間量最少
數據的最小冗餘編碼問題
在上例中,若假設 A, B, C, D 的編碼分別爲 0,00,1,01,則電文 ‘ABACCDA’ 便爲 ‘000011010’(共 9 位),但此編碼存在多義性:可譯爲: ‘BBCCDA’、‘ABACCDA’、‘AAAACCACA’ 等。
譯碼的唯一性問題
要求任一字符的編碼都不能是另外一字符編碼的前綴,這種編碼稱爲前綴編碼(實際上是非前綴碼)。 在編碼過程要考慮兩個問題,數據的最小冗餘編碼問題,譯碼的唯一性問題,利用最優二叉樹能夠很好地解決上述兩個問題
用二叉樹設計二進制前綴編碼
以電文中的字符做爲葉子結點構造二叉樹。而後將二叉樹中結點引向其左孩子的分支標 ‘0’,引向其右孩子的分支標 ‘1’; 每一個字符的編碼即爲從根到每一個葉子的路徑上獲得的 0, 1 序列。如此獲得的即爲二進制前綴編碼。
編碼: A:0, C:10,B:110,D:111
任意一個葉子結點都不可能在其它葉子結點的路徑中。
用哈夫曼樹設計總長最短的二進制前綴編碼
假設各個字符在電文中出現的次數(或頻率)爲 wi ,其編碼長度爲 li,電文中只有 n 種字符,則電文編碼總長爲:
設計電文總長最短的編碼,設計哈夫曼樹(以 n 種字符出現的頻率做權),
由哈夫曼樹獲得的二進制前綴編碼稱爲哈夫曼編碼
例:若是需傳送的電文爲 ‘ABACCDA’,即:A, B, C, D
的頻率(即權值)分別爲 0.43, 0.14, 0.29, 0.14,試構造哈夫曼編碼。
編碼: A:0, C:10, B:110, D:111 。電文 ‘ABACCDA’ 便爲 ‘0110010101110’(共 13 位)。
例:若是需傳送的電文爲 ‘ABCACCDAEAE’,即:A, B, C, D, E 的頻率(即權值)分別爲0.36, 0.1, 0.27, 0.1, 0.18,試構造哈夫曼編碼。
編碼: A:11,C:10,E:00,B:010,D:011 ,則電文 ‘ABCACCDAEAE’ 便爲 ‘110101011101001111001100’(共 24 位,比 33 位短)。
電文爲 「1101000」 ,譯文只能是「CAT」
哈夫曼編碼算法的實現
因爲哈夫曼樹中沒有度爲1的結點,則一棵有n個葉子的哈夫曼樹共有2×n-1個結點,能夠用一個大小爲2×n-1 的一維數組存放哈夫曼樹的各個結點。 因爲每一個結點同時還包含其雙親信息和孩子結點的信息,因此構成一個靜態三叉鏈表。
1 //haffman 樹的結構 2 typedef struct 3 { 4 //葉子結點權值 5 unsigned int weight; 6 //指向雙親,和孩子結點的指針 7 unsigned int parent; 8 unsigned int lChild; 9 unsigned int rChild; 10 } Node, *HuffmanTree; 11 12 //動態分配數組,存儲哈夫曼編碼 13 typedef char *HuffmanCode; 14 15 //選擇兩個parent爲0,且weight最小的結點s1和s2的方法實現 16 //n 爲葉子結點的總數,s1和 s2兩個指針參數指向要選取出來的兩個權值最小的結點 17 void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2) 18 { 19 //標記 i 20 int i = 0; 21 //記錄最小權值 22 int min; 23 //遍歷所有結點,找出單節點 24 for(i = 1; i <= n; i++) 25 { 26 //若是此結點的父親沒有,那麼把結點號賦值給 min,跳出循環 27 if((*huffmanTree)[i].parent == 0) 28 { 29 min = i; 30 break; 31 } 32 } 33 //繼續遍歷所有結點,找出權值最小的單節點 34 for(i = 1; i <= n; i++) 35 { 36 //若是此結點的父親爲空,則進入 if 37 if((*huffmanTree)[i].parent == 0) 38 { 39 //若是此結點的權值比 min 結點的權值小,那麼更新 min 結點,不然就是最開始的 min 40 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 41 { 42 min = i; 43 } 44 } 45 } 46 //找到了最小權值的結點,s1指向 47 *s1 = min; 48 //遍歷所有結點 49 for(i = 1; i <= n; i++) 50 { 51 //找出下一個單節點,且沒有被 s1指向,那麼i 賦值給 min,跳出循環 52 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 53 { 54 min = i; 55 break; 56 } 57 } 58 //繼續遍歷所有結點,找到權值最小的那一個 59 for(i = 1; i <= n; i++) 60 { 61 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 62 { 63 //若是此結點的權值比 min 結點的權值小,那麼更新 min 結點,不然就是最開始的 min 64 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 65 { 66 min = i; 67 } 68 } 69 } 70 //s2指針指向第二個權值最小的葉子結點 71 *s2 = min; 72 } 73 74 //建立哈夫曼樹並求哈夫曼編碼的算法以下,w數組存放已知的n個權值 75 void createHuffmanTree(HuffmanTree *huffmanTree, int w[], int n) 76 { 77 //m 爲哈夫曼樹總共的結點數,n 爲葉子結點數 78 int m = 2 * n - 1; 79 //s1 和 s2 爲兩個當前結點裏,要選取的最小權值的結點 80 int s1; 81 int s2; 82 //標記 83 int i; 84 // 建立哈夫曼樹的結點所需的空間,m+1,表明其中包含一個頭結點 85 *huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node)); 86 //1--n號存放葉子結點,初始化葉子結點,結構數組來初始化每一個葉子結點,初始的時候看作一個個單個結點的二叉樹 87 for(i = 1; i <= n; i++) 88 { 89 90 //其中葉子結點的權值是 w【n】數組來保存 91 (*huffmanTree)[i].weight = w[i]; 92 //初始化葉子結點(單個結點二叉樹)的孩子和雙親,單個結點,也就是沒有孩子和雙親,==0 93 (*huffmanTree)[i].lChild = 0; 94 (*huffmanTree)[i].parent = 0; 95 (*huffmanTree)[i].rChild = 0; 96 }// end of for 97 //非葉子結點的初始化 98 for(i = n + 1; i <= m; i++) 99 { 100 (*huffmanTree)[i].weight = 0; 101 (*huffmanTree)[i].lChild = 0; 102 (*huffmanTree)[i].parent = 0; 103 (*huffmanTree)[i].rChild = 0; 104 } 105 106 printf("\n HuffmanTree: \n"); 107 //建立非葉子結點,建哈夫曼樹 108 for(i = n + 1; i <= m; i++) 109 { 110 //在(*huffmanTree)[1]~(*huffmanTree)[i-1]的範圍內選擇兩個parent爲0 111 //且weight最小的結點,其序號分別賦值給s一、s2 112 select(huffmanTree, i-1, &s1, &s2); 113 //選出的兩個權值最小的葉子結點,組成一個新的二叉樹,根爲 i 結點 114 (*huffmanTree)[s1].parent = i; 115 (*huffmanTree)[s2].parent = i; 116 (*huffmanTree)[i].lChild = s1; 117 (*huffmanTree)[i].rChild = s2; 118 //新的結點 i 的權值 119 (*huffmanTree)[i].weight = (*huffmanTree)[s1].weight + (*huffmanTree)[s2].weight; 120 121 printf("%d (%d, %d)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight); 122 } 123 124 printf("\n"); 125 } 126 127 //哈夫曼樹創建完畢,從 n 個葉子結點到根,逆向求每一個葉子結點對應的哈夫曼編碼 128 void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n) 129 { 130 //指示biaoji 131 int i; 132 //編碼的起始指針 133 int start; 134 //指向當前結點的父節點 135 int p; 136 //遍歷 n 個葉子結點的指示標記 c 137 unsigned int c; 138 //分配n個編碼的頭指針 139 huffmanCode=(HuffmanCode *)malloc((n+1) * sizeof(char *)); 140 //分配求當前編碼的工做空間 141 char *cd = (char *)malloc(n * sizeof(char)); 142 //從右向左逐位存放編碼,首先存放編碼結束符 143 cd[n-1] = '\0'; 144 //求n個葉子結點對應的哈夫曼編碼 145 for(i = 1; i <= n; i++) 146 { 147 //初始化編碼起始指針 148 start = n - 1; 149 //從葉子到根結點求編碼 150 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) 151 { 152 if( (*huffmanTree)[p].lChild == c) 153 { 154 //從右到左的順序編碼入數組內 155 cd[--start] = '0'; //左分支標0 156 } 157 else 158 { 159 cd[--start] = '1'; //右分支標1 160 } 161 }// end of for 162 //爲第i個編碼分配空間 163 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); 164 165 strcpy(huffmanCode[i], &cd[start]); 166 } 167 168 free(cd); 169 //打印編碼序列 170 for(i = 1; i <= n; i++) 171 { 172 printf("HuffmanCode of %3d is %s\n", (*huffmanTree)[i].weight, huffmanCode[i]); 173 } 174 175 printf("\n"); 176 } 177 178 int main(void) 179 { 180 HuffmanTree HT; 181 HuffmanCode HC; 182 int *w,i,n,wei,m; 183 184 printf("\nn = " ); 185 186 scanf("%d",&n); 187 188 w=(int *)malloc((n+1)*sizeof(int)); 189 190 printf("\ninput the %d element's weight:\n",n); 191 192 for(i=1; i<=n; i++) 193 { 194 printf("%d: ",i); 195 fflush(stdin); 196 scanf("%d",&wei); 197 w[i]=wei; 198 } 199 200 createHuffmanTree(&HT, w, n); 201 creatHuffmanCode(&HT,&HC,n); 202 203 return 0; 204 }
補充:樹的計數
已知先序序列和中序序列可肯定一棵惟一的二叉樹;
已知後序序列和中序序列可肯定一棵惟一的二叉樹;
已知先序序列和後序序列不能肯定一棵惟一的二叉樹。