一.背景介紹:
給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
二.實現步驟:
1.構造一棵哈夫曼樹
2.根據創建好的哈夫曼樹創建一張哈夫曼編碼表
3.輸入一串哈夫曼序列,輸出原始字符
三.設計思想:
1.首先要構造一棵哈夫曼樹,哈夫曼樹的結點結構包括權值,雙親,左右孩子;假如由n個字符來構造一棵哈夫曼樹,則共有結點2n-1個;在構造前,先初始化,初始化操作是把雙親,左右孩子的下標值都賦為0;然后依次輸入每個結點的權值
2.第二步是通過n-1次循環,每次先找輸入的權值中最小的兩個結點,把這兩個結點的權值相加賦給一個新結點,,並且這個新結點的左孩子是權值最小的結點,右孩子是權值第二小的結點;鑒於上述找到的結點都是雙親為0的結點,為了下次能正確尋找到剩下結點中權值最小的兩個結點,每次循環要把找的權值最小的兩個結點的雙親賦值不為0(i).就這樣通過n-1循環下、操作,創建了一棵哈夫曼樹,其中,前n個結點是葉子(輸入的字符結點)后n-1個是度為2的結點
3.編碼的思想是逆序編碼,從葉子結點出發,向上回溯,如果該結點是回溯到上一個結點的左孩子,則在記錄編碼的數組里存“0”,否則存“1”,注意是倒着存;直到遇到根結點(結點雙親為0),每一次循環編碼到根結點,把編碼存在編碼表中,然后開始編碼下一個字符(葉子)
4.譯碼的思想是循環讀入一串哈夫曼序列,讀到“0”從根結點的左孩子繼續讀,讀到“1”從右孩子繼續,如果讀到一個結點的左孩子和右孩子是否都為0,如果是說明已經讀到了一個葉子(字符),翻譯一個字符成功,把該葉子結點代表的字符存在一個存儲翻譯字符的數組中,然后繼續從根結點開始讀,直到讀完這串哈夫曼序列,遇到結束符便退出翻譯循環
四.源代碼:

1 /*************************************** 2 目的:1.根據輸入的字符代碼集及其權值集, 3 構造赫夫曼樹,輸出各字符的赫夫曼編碼 4 2.輸入赫夫曼碼序列,輸出原始字符代碼 5 作者:Dmego 時間:2016-11-11 6 ****************************************/ 7 #include<iostream> 8 #define MAX_MA 1000 9 #define MAX_ZF 100 10 using namespace std; 11 12 //哈夫曼樹的儲存表示 13 typedef struct 14 { 15 int weight; //結點的權值 16 int parent, lchild, rchild;//雙親,左孩子,右孩子的下標 17 }HTNode,*HuffmanTree; //動態分配數組來儲存哈夫曼樹的結點 18 19 //哈夫曼編碼表的儲存表示 20 typedef char **HuffmanCode;//動態分配數組存儲哈夫曼編碼 21 22 //返回兩個雙親域為0且權值最小的點的下標 23 void Select(HuffmanTree HT, int n, int &s1, int &s2) 24 { 25 /*n代表HT數組的長度 26 */ 27 28 //前兩個for循環找所有結點中權值最小的點(字符) 29 for (int i = 1; i <= n; i++) 30 {//利用for循環找出一個雙親為0的結點 31 if (HT[i].parent == 0) 32 { 33 s1 = i;//s1初始化為i 34 break;//找到一個后立即退出循環 35 } 36 } 37 for (int i = 1; i <= n; i++) 38 {/*利用for循環找到所有結點(字符)權值最小的一個 39 並且保證該結點的雙親為0*/ 40 if (HT[i].weight < HT[s1].weight && HT[i].parent == 0) 41 s1 = i; 42 } 43 //后兩個for循環所有結點中權值第二小的點(字符) 44 for (int i = 1; i <= n; i++) 45 {//利用for循環找出一個雙親為0的結點,並且不能是s1 46 if (HT[i].parent == 0 && i != s1) 47 { 48 s2 = i;//s2初始化為i 49 break;//找到一個后立即退出循環 50 } 51 } 52 53 for (int i = 1; i <= n; i++) 54 {/*利用for循環找到所有結點(字符)權值第二小的一個, 55 該結點滿足不能是s1且雙親是0*/ 56 if (HT[i].weight < HT[s2].weight && HT[i].parent == 0 && i!= s1) 57 s2 = i; 58 } 59 60 } 61 62 //構造哈夫曼樹 63 void CreateHuffmanTree(HuffmanTree &HT, int n) 64 { 65 /*-----------初始化工作-------------------------*/ 66 if (n <= 1) 67 return; 68 int m = 2 * n - 1; 69 HT = new HTNode[m + 1]; 70 for (int i = 1; i <= m; ++i) 71 {//將1~m號單元中的雙親,左孩子,右孩子的下標都初始化為0 72 HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0; 73 } 74 for (int i = 1; i <= n; ++i) 75 { 76 cin >> HT[i].weight;//輸入前n個單元中葉子結點的權值 77 } 78 /*-----------創建工作---------------------------*/ 79 int s1,s2; 80 for (int i = n + 1; i <= m; ++i) 81 {//通過n-1次的選擇,刪除,合並來構造哈夫曼樹 82 Select(HT, i - 1, s1, s2); 83 /*cout << HT[s1].weight << " , " << HT[s2].weight << endl;*/ 84 /*將s1,s2的雙親域由0改為i 85 (相當於把這兩個結點刪除了,這兩個結點不再參與Select()函數)*/ 86 HT[s1].parent = i; 87 HT[s2].parent = i; 88 //s1,與s2分別作為i的左右孩子 89 HT[i].lchild = s1; 90 HT[i].rchild = s2; 91 //結點i的權值為s1,s2權值之和 92 HT[i].weight = HT[s1].weight + HT[s2].weight; 93 } 94 } 95 96 //從葉子到根逆向求每個字符的哈夫曼編碼,儲存在編碼表HC中 97 void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n) 98 { 99 HC = new char*[n + 1];//分配儲存n個字符編碼的編碼表空間 100 char *cd = new char[n];//分配臨時存儲字符編碼的動態空間 101 cd[n - 1] = '\0';//編碼結束符 102 for (int i = 1; i <= n; i++)//逐個求字符編碼 103 { 104 int start = n - 1;//start 開始指向最后,即編碼結束符位置 105 int c = i; 106 int f = HT[c].parent;//f指向結點c的雙親 107 while (f != 0)//從葉子結點開始回溯,直到根結點 108 { 109 --start;//回溯一次,start向前指向一個位置 110 if (HT[f].lchild == c) cd[start] = '0';//結點c是f的左孩子,則cd[start] = 0; 111 else cd[start] = '1';//否則c是f的右孩子,cd[start] = 1 112 c = f; 113 f = HT[f].parent;//繼續向上回溯 114 } 115 HC[i] = new char[n - start];//為第i個字符編碼分配空間 116 strcpy(HC[i], &cd[start]);//把求得編碼的首地址從cd[start]復制到HC的當前行中 117 } 118 delete cd; 119 } 120 121 //哈夫曼譯碼 122 void TranCode(HuffmanTree HT,char a[],char zf[],char b[],int n) 123 { 124 /* 125 HT是已經創建好的哈夫曼樹 126 a[]用來傳入二進制編碼 127 b[]用來記錄譯出的字符 128 zf[]是與哈夫曼樹的葉子對應的字符(葉子下標與字符下標對應) 129 n是字符個數,相當於zf[]數組得長度 130 */ 131 132 int q = 2*n-1;//q初始化為根結點的下標 133 int k = 0;//記錄存儲譯出字符數組的下標 134 int i = 0; 135 for (i = 0; a[i] != '\0';i++) 136 {//for循環結束條件是讀入的字符是結束符(二進制編碼) 137 //此代碼塊用來判斷讀入的二進制字符是0還是1 138 if (a[i] == '0') 139 {/*讀入0,把根結點(HT[q])的左孩子的下標值賦給q 140 下次循環的時候把HT[q]的左孩子作為新的根結點*/ 141 q = HT[q].lchild; 142 } 143 else if (a[i] == '1') 144 { 145 q = HT[q].rchild; 146 } 147 //此代碼塊用來判斷HT[q]是否為葉子結點 148 if (HT[q].lchild == 0 && HT[q].rchild == 0) 149 {/*是葉子結點,說明已經譯出一個字符 150 該字符的下標就是找到的葉子結點的下標*/ 151 b[k++] = zf[q];//把下標為q的字符賦給字符數組b[] 152 q = 2 * n - 1;//初始化q為根結點的下標 153 //繼續譯下一個字符的時候從哈夫曼樹的根結點開始 154 } 155 } 156 /*譯碼完成之后,用來記錄譯出字符的數組由於沒有結束符輸出的 157 時候回報錯,故緊接着把一個結束符加到數組最后*/ 158 b[k] = '\0'; 159 } 160 //菜單函數 161 void menu() 162 { 163 cout << endl; 164 cout << " ┏〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┓" << endl; 165 cout << " ┃ ★★★★★★★哈夫曼編碼與譯碼★★★★★★★ ┃" << endl; 166 cout << " ┃ 1. 創建哈夫曼樹 ┃" << endl; 167 cout << " ┃ 2. 進行哈夫曼編碼 ┃" << endl; 168 cout << " ┃ 3. 進行哈夫曼譯碼 ┃" << endl; 169 cout << " ┃ 4. 退出程序 ┃" << endl; 170 cout << " ┗〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┛" << endl; 171 cout << " <><注意:空格字符用'- '代替><>" << endl; 172 cout << endl; 173 } 174 void main() 175 { 176 int falg;//記錄要編碼的字符個數 177 char a[MAX_MA];//儲存輸入的二進制字符 178 char b[MAX_ZF];//存儲譯出的字符 179 char zf[MAX_ZF];//儲存要編碼的字符 180 HuffmanTree HT = NULL;//初始化樹為空數 181 HuffmanCode HC = NULL;//初始化編碼表為空表 182 menu(); 183 while (true) 184 { 185 int num; 186 cout << "<><請選擇功能(1-創建 2-編碼 3-譯碼 4-退出)><>: "; 187 cin >> num; 188 switch (num) 189 { 190 case 1 : 191 cout << "<><請輸入字符個數><>:"; 192 cin >> falg; 193 //動態申請falg個長度的字符數組,用來存儲要編碼的字符 194 /*char *zf = new char[falg];*/ 195 cout << "<><請依次輸入" << falg << "個字符:><>: "; 196 for (int i = 1; i <= falg; i++) 197 cin >> zf[i]; 198 cout << "<><請依次輸入" << falg << "個字符的權值><>: "; 199 CreateHuffmanTree(HT, falg);//調用創建哈夫曼樹的函數 200 cout << endl; 201 cout << "<><創建哈夫曼成功!,下面是該哈夫曼樹的參數輸出><>:" << endl; 202 cout << endl; 203 cout << "結點i"<<"\t"<<"字符" << "\t" << "權值" << "\t" << "雙親" << "\t" << "左孩子" << "\t" << "右孩子" << endl; 204 for (int i = 1; i <= falg * 2 - 1; i++) 205 { 206 cout << i << "\t"<<zf[i]<< "\t" << HT[i].weight << "\t" << HT[i].parent << "\t" << HT[i].lchild << "\t" << HT[i].rchild << endl; 207 } 208 cout << endl; 209 break; 210 case 2: 211 CreatHuffmanCode(HT, HC, falg);//調用創建哈夫曼編碼表的函數 212 cout << endl; 213 cout << "<><生成哈夫曼編碼表成功!,下面是該編碼表的輸出><>:" << endl; 214 cout << endl; 215 cout << "結點i"<<"\t"<<"字符" << "\t" << "權值" << "\t" << "編碼" << endl; 216 for (int i = 1; i <= falg; i++) 217 { 218 cout << i << "\t"<<zf[i]<< "\t" << HT[i].weight << "\t" << HC[i] << endl; 219 } 220 cout << endl; 221 break; 222 case 3: 223 cout << "<><請輸入想要翻譯的一串二進制編碼><>:"; 224 /*這樣可以動態的直接輸入一串二進制編碼, 225 因為這樣輸入時最后系統會自動加一個結束符*/ 226 cin >> a; 227 TranCode(HT, a, zf, b, falg);//調用譯碼的函數, 228 /*這樣可以直接把數組b輸出,因為最后有 229 在數組b添加輸出時遇到結束符會結束輸出*/ 230 cout << endl; 231 cout << "<><譯碼成功!翻譯結果為><>:" << b << endl; 232 cout << endl; 233 break; 234 case 4: 235 cout << endl; 236 cout << "<><退出成功!><>" << endl; 237 exit(0); 238 default: 239 break; 240 } 241 } 242 243 //-abcdefghijklmnopqrstuvwxyz 244 //186 64 13 22 32 103 21 15 47 57 1 5 32 20 57 63 15 1 48 51 80 23 8 18 1 16 1 245 //000101010111101111001111110001100100101011110110 246 247 }
五.運行截圖: