1. 程式人生 > >樹和二叉樹->最優二叉樹

樹和二叉樹->最優二叉樹

nco 代碼實現 type except close 輸出結點 eof fde 左右

文字描述

結點的路徑長度

  從樹中一個結點到另一個結點之間的分支構成這兩個結點之間的路徑,路徑上的分支數目稱作路徑長度。

樹的路徑長度

   從樹根到每一個結點的路徑長度之和叫樹的路徑長度。

結點的帶權路徑長度

  從該結點到樹根之間的路徑長度與結點上權的乘積

樹的帶權路徑長度

  所有葉子結點的帶權路徑長度之和

最優二叉樹或哈夫曼樹

  假設有n個權值{w1,w2, … ,wn},試構造一顆有n個葉子結點的二叉樹,每個葉子結點帶權wi,則其中帶權路徑長度WPL最小的二叉樹稱作最優二叉樹或哈夫曼樹。

哈夫曼算法

  目的:構造一顆哈夫曼樹

  實現:

  (1) 根據給定的n個權值{w1,w2, … ,wn}構成n棵二叉樹的集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權為wi的根結點,其左右子樹均為空。

  (2) 在F中選取兩顆根結點的權值最小的樹作為左右子樹構造一顆新的二叉樹,且置新的二叉樹的根結點的權值為其左右子樹上根結點的權值之和。

  (3) 在F中刪除這兩顆樹,同時將新得到的二叉樹加入F中

  (4) 重復(2)和(3),直到F只含一顆樹為止。這顆樹便是哈夫曼樹。

哈夫曼編碼

  假設A、B、C、D的編碼分別為00,01,10和11,則字符串’ABACCDA‘的電文便是’00010010101100’,對方接受時,可按二位一分進行譯碼。但有時希望電文總長盡可能的短, 如果讓電文中出現次數較多的字符采用盡可能短的編碼,則傳送電文的總長鞭可減少。但是設計長短不等的編碼,則必須是任一字符的編碼都不是另一個字符的編碼的前綴,以避免同樣子電文串有多種譯法,這種編碼叫前綴編碼。

  又如何得到使電文總長最短的二進制前綴編碼呢?假設字符串中有n種字符,每種字符在電文中出現的次數作為權,設計一顆哈夫曼樹。左子樹為0,右子樹為1,走一條從根到葉子結點的路徑進行編碼。

哈夫曼譯碼

譯碼的過程是分解電文字符串,從根出發,按字符‘0‘或’1‘確定找左孩子或右孩子,直至葉子結點,便求得該子串相應的字符。

示意圖

技術分享圖片

技術分享圖片

算法分析

代碼實現

技術分享圖片
  1 /*
  2  ./a.out 5 29 7 8 14 23 3 11
  3  
  4  1001111100110001110
  5  */
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8
#include <string.h> 9 10 #define DEBUG 11 //結點的最大個數 12 #define MAX_SIZE 50 13 14 //哈夫曼樹和哈夫曼結點的存儲結構 15 typedef struct{ 16 unsigned int weight; 17 unsigned int parent, lchild, rchild; 18 }HNode, *HuffmanTree; 19 20 //哈夫曼編碼表的存儲結構 21 typedef char **HuffmanCode; 22 23 /* 24 * 在HT[1, ..., l]中選擇parent為0且weight最小的兩個結點,其序號分別為min1,min2 25 */ 26 int Select(HuffmanTree HT, int l, int *min1, int *min2){ 27 int i = 0; 28 *min1 = *min2 = -1; 29 30 //找parent為0且weight最小的min1 31 for(i=1; i<=l; i++){ 32 if(!HT[i].parent){ 33 if(*min1<0){ 34 *min1 = i; 35 }else if(HT[*min1].weight > HT[i].weight){ 36 *min1 = i; 37 }else{ 38 //other 39 } 40 }else{ 41 //other 42 } 43 } 44 45 //找parent為0且weight最小的min2 46 for(i = 1; i<=l; i++){ 47 if(!HT[i].parent && i!=*min1){ 48 if(*min2<0){ 49 *min2 = i; 50 }else if(HT[*min2].weight > HT[i].weight){ 51 *min2 = i; 52 }else{ 53 //other 54 } 55 }else{ 56 //other 57 } 58 } 59 60 //確保min1<min2 61 if(*min1 > *min2){ 62 i = *min1; 63 *min1 = *min2; 64 *min2 = i; 65 } 66 67 if((*min1<0) && (*min2<0)){ 68 return -1; 69 }else{ 70 return 0; 71 } 72 } 73 74 /* 75 * 創建一顆哈夫曼樹 76 * 77 * w存放n個字符的權值(權值都為非負數),構造哈夫曼樹HT, L表示哈夫曼樹的長度(正常情況下為2*n-1) 78 */ 79 void HuffmanCreate(int w[], int n, HuffmanTree *HT, int *L){ 80 if(n<=1){ 81 return; 82 } 83 int m = 2*n-1, i = 0, s1 = 0, s2 = 0; 84 //0號單元不用 85 (*HT) = (HuffmanTree)malloc((m+1)*sizeof(HNode)); 86 HuffmanTree p = *HT; 87 *L = m; 88 89 //給哈夫曼樹的葉子結點賦值 90 for(p=(*HT+1), i = 0; i<n; i++){ 91 p[i].weight = w[i]; 92 p[i].parent = p[i].lchild = p[i].rchild = 0; 93 } 94 95 //給哈夫曼樹的非葉子結點賦值 96 for(i=n+1; i<=m; ++i){ 97 //在HT[1,...,i-1]中選擇parent為0且weight最小的兩個結點,其序號分別為s1和s2 98 if( Select(*HT, i-1, &s1, &s2) < 0){ 99 break; 100 } 101 //用s1和s2作為左右子樹構造一顆新的二叉樹 102 (*HT)[s1].parent = (*HT)[s2].parent = i; 103 (*HT)[i].lchild = s1; 104 (*HT)[i].rchild = s2; 105 //置新的二叉樹的根結點的權值為其左右子樹上根結點的權值之和 106 (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight; 107 } 108 return; 109 } 110 111 /* 112 * 哈夫曼編碼 113 * 114 * 從葉子到根逆向求每個字符的哈夫曼編碼 115 */ 116 void HuffmanEncoding_1(HuffmanTree HT, HuffmanCode *HC, int n){ 117 //分配n個字符編碼的頭指針向量 118 *HC = (HuffmanCode)malloc((n+1)*sizeof(char *)); 119 //分配求編碼的工作空間 120 char *cd = (char*)malloc(n+sizeof(char)); 121 //編碼結束符 122 cd[n-1] = \0; 123 int i = 0, start = 0, c = 0, f = 0; 124 //逐個字符求哈夫曼編碼 125 for(i=1; i<=n; i++){ 126 //編碼結束位置 127 start = n-1; 128 //從葉子到根逆向求編碼 129 for(c=i, f=HT[i].parent; f!=0; c = f, f=HT[f].parent){ 130 if(HT[f].lchild == c){ 131 cd[--start] = 0; 132 }else{ 133 cd[--start] = 1; 134 } 135 } 136 //為第i個字符編碼分配空間 137 (*HC)[i-1] = (char*)malloc((n-start)*sizeof(char)); 138 //從cd復制編碼到HC 139 strncpy((*HC)[i-1], cd+start, n-start); 140 } 141 //釋放工作空間 142 free(cd); 143 return ; 144 } 145 146 /* 147 * 哈夫曼編碼 148 * 149 * 從根出發,遍歷整個哈夫曼樹,求得各個葉子結點所表示的字符的哈夫曼編碼 150 */ 151 void HuffmanEncoding_2(HuffmanTree HT, HuffmanCode *HC, int n){ 152 *HC = (HuffmanCode)malloc((n+1)*sizeof(char *)); 153 int p = 2*n-1; 154 int cdlen = 0; 155 int i = 0; 156 char *cd = (char*)malloc(n+sizeof(char)); 157 //利用weight,當作遍歷哈夫曼樹時的結點狀態標誌 158 for(i=1; i<=(2*n-1); i++){ 159 HT[i].weight = 0; 160 } 161 while(p){ 162 if(HT[p].weight == 0){ 163 //向左 164 HT[p].weight = 1; 165 if(HT[p].lchild != 0){ 166 p = HT[p].lchild; 167 cd[cdlen++] = 0; 168 }else if(HT[p].rchild == 0){ 169 //登記葉子結點的字符的編碼 170 (*HC)[p-1] = (char *)malloc((cdlen+1)*sizeof(char)); 171 memset((*HC)[p-1], 0, (cdlen+1)*sizeof(char)); 172 cd[cdlen] = \0; 173 //復制編碼 174 strncpy((*HC)[p-1], cd, cdlen); 175 } 176 }else if(HT[p].weight == 1){ 177 //向右 178 HT[p].weight = 2; 179 if(HT[p].rchild != 0){ 180 p = HT[p].rchild; 181 cd[cdlen++] = 1; 182 } 183 }else{ 184 //退到父結點,編碼長度減1 185 HT[p].weight = 0; 186 p = HT[p].parent; 187 --cdlen; 188 } 189 } 190 } 191 192 /* 193 *哈夫曼譯碼 194 * 195 *從根出發走向葉子結點,找到葉子結點後輸出該結點的下標值,然後返回根結點重復,直至讀完。 196 */ 197 void HuffmanDecoding(HuffmanTree HT, int n, char str[], size_t len) 198 { 199 //1001111100110001110 200 int p = 2*n-1; 201 int i = 0; 202 while(i < len){ 203 printf("%c",str[i]); 204 if(str[i] == 0){ 205 //走向左孩子 206 p = HT[p].lchild; 207 }else if(str[i] == 1){ 208 //走向右孩子 209 p = HT[p].rchild; 210 }else{ 211 printf("unexcepted char %c!!!\n", str[i]); 212 return; 213 } 214 //看是否走到葉子結點 215 if(p<=n){ 216 //輸出結點的下標值,並返回根結點 217 printf("\t=\t%d\n", p); 218 p = 2*n-1; 219 } 220 i+=1; 221 } 222 return ; 223 } 224 225 void HuffmanPrint(HuffmanTree HT, int L){ 226 if(L<=0) 227 return ; 228 int i = 0; 229 for(i=0; i<=L; i++){ 230 printf("%d:\t{w=%d\tp=%d\tl=%d\tr=%d}\n", i, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild); 231 } 232 } 233 234 int main(int argc, char *argv[]) 235 { 236 if(argc < 2){ 237 return -1; 238 } 239 240 int i = 0, L = 0; 241 int w[MAX_SIZE] = {0}; 242 HuffmanTree HT = NULL; 243 244 for(i=1; i<argc; i++){ 245 w[i-1] = atoi(argv[i]); 246 } 247 #ifdef DEBUG 248 printf("創建並打印哈夫曼樹:\n"); 249 #endif 250 HuffmanCreate(w, i-1, &HT, &L); 251 HuffmanPrint(HT, L); 252 253 #ifdef DEBUG 254 printf("從葉子結點到根逆向求哈夫曼編碼:\n"); 255 #endif 256 HuffmanCode HC; 257 HuffmanEncoding_1(HT, &HC, i-1); 258 259 #ifdef DEBUG 260 int j = 0; 261 for(j=0; j<i-1; j++){ 262 printf("%d:\t{w:%d\tcode=%s}\n", j+1, w[j], HC[j]); 263 free(HC[j]); 264 } 265 free(HC); 266 #endif 267 268 #ifdef DEBUG 269 printf("從根出發求哈夫曼編碼:\n"); 270 #endif 271 HuffmanEncoding_2(HT, &HC, i-1); 272 273 #ifdef DEBUG 274 for(j=0; j<i-1; j++){ 275 printf("%d:\t{w:%d\tcode=%s}\n", j+1, w[j], HC[j]); 276 free(HC[j]); 277 } 278 free(HC); 279 #endif 280 281 printf("按照上述編碼指示,輸入一串0/1字符串以進行譯碼:"); 282 char str[100] = {0}; 283 scanf("%s", &str); 284 HuffmanDecoding(HT, i-1, str, strlen(str)); 285 return 0; 286 }
最優二叉樹

運行

技術分享圖片

樹和二叉樹->最優二叉樹