赫夫曼樹的介紹(寫的不好地方大佬請指教)
最優二叉樹又稱哈夫曼樹,是帶權路徑最短的二叉樹。根據節點的個數,權值的不同,最優二叉樹的形狀也不同。
圖 6-34 是 3 棵最優二叉樹的例子,它們共同的特點是帶權節點都是葉子節點,權值越小,就離根節點也遠,那么我們是如何構建這顆最優二叉樹
步驟如下:

那如何創建這一個哈夫曼樹呢?(百度百科)
假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設為 w1、w2、…、wn,則哈夫曼樹的構造規則為:
(1) 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結點);
(2) 在森林中選出兩個根結點的權值最小的樹合並,作為一棵新樹的左、右子樹,且新樹的根結點權值為其左、右子樹根結點權值之和;
(3)從森林中刪除選取的兩棵樹,並將新樹加入森林;
(4)重復(2)、(3)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。
如:對 下圖中的六個帶權葉子結點來構造一棵哈夫曼樹,步驟如下:

當建立好哈夫曼樹,我們要將其進行編碼,要將權值表示改為0,1表示,葉子節點的左邊改為0,右邊改為1
這樣子比較方便在網絡上傳輸,因為哈夫曼樹的研究目的就是為了解決早期遠距離通信(電報)的數據傳輸的優化問題。

接下來我們來分析一下,這樣做對數據的優化體現在哪里?
例如,如上圖。6-12-9,
假如們要傳輸一端報文:BADCADFEED,那么我們可以用相應的二進制表示
| 字母 | a | b | c | d | e | f |
| 二進制字符 | 000 | 001 | 010 | 011 | 100 | 101 |
這樣真正的傳輸的數據編碼二進制后就是:001000011010000011101100100011(共30個字符),可見如果傳輸數據過大,這個報文的編碼也就越大了。
但是如果我們按照上面的哈夫曼樹進行編碼的話
| 字母 | a | b | c | d | e | f |
| 二進制字符 | 01 | 1001 | 101 | 00 | 11 | 1000 |
新編碼后的數據是:1001010010101001000111100(共25個字符)
大約節約了17%的存儲或傳輸文本,隨着字符的增加和多字符權重的不同,這種壓縮會更加突顯出來優勢。
0和1是比較容易混淆的,為了設計出來長度不相等的編碼,我們就必須有一種規定,就是任一字符的編碼都不是另一個字符的編碼的前綴,這種編碼被稱為前綴編碼。
我們可以發現通過哈夫曼編碼形成的每個節點的編碼例如:1000,1000混淆的10,100的類似的編碼了。
當接收者收到了這個已經經過編碼的二進制數后,我們要如何進行解碼,才能看到發送者真正想發給接收者的消息呢?
解碼的時候,必須用到雙方約定好的哈夫曼樹:

從根節點開始遍歷,就可以知道A是01,E是11,B是 1001 ,其余的節點信息也可以相應的得到,從而成功解碼。
哈夫曼樹的節點存儲結構
1 //哈夫曼樹結構
2 ; typedef struct{ 3 unsigned int weight; //權重
4 unsigned int parent, lchild, rchild; //樹的雙親節點,和左右孩子
5
6 }HTNode, *HuffmanTree; 7
8 typedef char** HuffmanCode;
基本思路就是:
1、首先我們要建立一個哈夫曼樹。
2、這個哈夫曼樹有的特點在上面有介紹。
3、對哈夫曼樹進行0,1編碼
4,最后打印出已經編碼完成的哈夫曼樹。
里面最難的兩部步應該是建樹,解碼輸出(可能我比較笨把,搞了二天才搞明白)
1、建樹,根據節點的權重建立,每次從的序列中比較出兩個最小的權重,建立出一顆樹,然后再從剩余的節點中繼續抽取節點權重最小的節點,繼續建樹,這邊我采用的是迭代方式;
2、解碼輸出,采用的是葉子節點逆序遍歷到根節點,將0,1存儲到字符數組里面,然后再將數組輸出。
里面是具體實現的代碼,里面也有注釋
1 //函數聲明
2 int Min(HuffmanTree T, int i); //求i個節點中的最小權重的序列號,並返回
3 void Select(HuffmanTree T, int i, int& s1, int& s2); //從兩個最小權重中選取最小的(左邊)給s1,右邊的給s2
4 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n);//哈夫曼編碼與解碼
函數的具體實現算法1:
1 //返回i個節點中權值最小的樹的根節點的序號,供select()調用
2 int Min(HuffmanTree T, int i){ 3 int j, flag; 4 unsigned int k = UINT_MAX; //%d-->UINT_MAX = -1,%u--->非常大的數
5 for (j = 1; j <= i; j++) 6 if (T[j].weight < k && T[j].parent == 0) 7 k = T[j].weight, flag = j; // 8 T[flag].parent = 1; //將parent標志為1避免二次查找
9
10 return flag; //返回節點的下標
11 } 12
13 void Select(HuffmanTree T, int i,int& s1,int& s2){ 14 //在i個節點中選取2個權值最小的樹的根節點序號,s1為序號較小的那個
15 int j; 16 s1 = Min(T,i); 17 s2 = Min(T,i); 18 if (s1 > s2){ 19 j = s1; 20 s1 = s2; 21 s2 = j; 22 } 23 }
解碼算法1(從根節點遍歷赫夫曼樹逆序輸出):
1 //HuffmanCode代表的樹解碼二進制值
2 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n){ 3 //w存放n個字符的權值(均>0),構造哈夫曼樹HT,並求出n個字符的哈夫曼編碼HC
4 int m, i, s1, s2, start; 5 unsigned c, f; 6 char* cd; 7 //分配存儲空間
8 HuffmanTree p; 9 if (n <= 1) 10 return; 11 //n個字符(葉子節點)有2n-1個樹節點,所以樹節點m
12 m = 2 * n - 1; 13 HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode)); //0號元素未用 14 //這一步是給哈夫曼樹的葉子節點初始化
15 for (p = HT + 1, i = 1; i <= n; ++i, ++p, ++w) 16 { 17 (*p).weight = *w; 18 (*p).lchild = 0; 19 (*p).rchild = 0; 20 (*p).parent = 0; 21 } 22 //這一步是給哈夫曼樹的非葉子節點初始化
23 for (; i <= m; ++i, ++p){ 24 (*p).parent = 0; 25 } 26 /************************************************************************/
27 /* 做完准備工作后 ,開始建立哈夫曼樹 28 /************************************************************************/
29 for (i = n + 1; i <= m; i++) 30 { 31 //在HT[1~i-1]中選擇parent=0且weigh最小的節點,其序號分別為s1,s2
32 Select(HT, i - 1, s1, s2); //傳引用
33 HT[s1].parent = HT[s2].parent = i; 34 HT[i].lchild = s1; 35 HT[i].rchild = s2; 36 HT[i].weight = HT[s1].weight + HT[s2].weight; 37 } 38 /************************************************************************/
39 /* 從葉子到根逆求每個葉子節點的哈夫曼編碼 */
40 /************************************************************************/
41 //分配n個字符編碼的頭指針向量,([0]不用)
42 HC = (HuffmanCode)malloc((n + 1)*sizeof(char*)); 43 cd = (char*)malloc(n*sizeof(char)); //分配求編碼的工作空間
44 cd[n - 1] = '\0'; //結束符
45 for (i = 1; i <= n; i++) //每個節點的遍歷
46 { 47 start = n - 1; c = i; f = HT[i].parent; //c表示當前節點的下標
48 while (f != 0){ //父節點不為0,即不為根節點
49 --start; 50 if (HT[i].lchild == c)cd[start] = '0'; 51 else
52 cd[start] = '1'; 53 c = f; f = HT[f].parent; //迭代向上回溯
54 } 55 HC[i] = (char*)malloc((n - start)*sizeof(char)); //生成一個塊內存存儲字符 56 //為第i個字符編碼分配空間
57 strcpy(HC[i], &cd[start]); //從cd賦值字符串到cd
58 } 59 free(cd); //釋放資源
60 }
這里面有一個地方鑽牛角尖了,卡了一段時間,那就是我們輸入的權重是存儲在w這塊內存里面的,並通過權重建立起來的樹,於是HT內存存儲的第一個節點也就是我們輸入權重所對應的第一個節點。於是我們開始逆序輸出解碼時,所對應的每個節點的解碼與權重所對應的一一對應。
解碼算法2(從根節點正序遍歷赫夫曼樹輸出):
1 //利用無棧遞歸的思想
2 void HuffmanCoding2(HuffmanTree &HT, HuffmanCode &HC, int* weight, int n){ 3 int m, i, s1, s2; 4 unsigned c, cdlen; 5 HuffmanTree p; 6 char* cd; //編碼空間
7
8 if (n <= 1) 9 return; 10 m = 2 * n - 1; 11 HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode)); //1開始到m+1//總共2n-1
12 for (p = HT + 1, i = 1; i <= n; ++i, ++weight, ++p){ 13 // HT[i].weight = *weight; 14 // HT[i].parent = 0; 15 // HT[i].lchild = 0; 16 // HT[i].rchild = 0;
17 (*p).weight = *weight; 18 (*p).parent = 0; 19 (*p).lchild = 0; 20 (*p).rchild = 0; 21 } 22 for (; i <= m; ++i,++p) 23 (*p).parent = 0; 24 /************************************************************************/
25 /* 將樹的葉子節點和即將存儲的雙親節點初始化后,開始建立赫夫曼樹 */
26 /************************************************************************/
27
28 for (i = n + 1; i <= m; ++i) //i++ --->++i
29 { 30 Select(HT, i - 1, s1, s2); 31 HT[s1].parent = HT[s2].parent=i; 32 HT[i].lchild = s1; 33 HT[i].rchild = s2; 34 HT[i].weight = HT[s1].weight + HT[s2].weight; 35 } 36
37 c = m; //c = 2*n-1
38 HC = (HuffmanCode)malloc((n + 1)*sizeof(char*)); 39 cd = (char*)malloc(n*sizeof(char)); 40 cdlen = 0; 41 for (i = 1; i <= m; i++) 42 HT[i].weight = 0; //將所有的權重置0 43
44 //這是一個迭代的過程
45 while (c){ 46 if (HT[c].weight == 0){ 47 //向左
48 HT[c].weight = 1; 49 if (HT[c].lchild != 0){ 50 c = HT[c].lchild; 51 cd[cdlen++] = '0'; 52 } 53 else if (HT[c].rchild == 0) 54 { 55 cd[cdlen] = '\0'; 56 HC[c] = (char*)malloc(sizeof(char)*(cdlen + 1)); 57 strcpy(HC[c], cd); //復制編碼串
58 } 59 } 60 else if (HT[c].weight == 1) 61 { 62 //向右遍歷
63 HT[c].weight = 2; 64 if (HT[c].rchild != 0){ //存在右孩子
65 c = HT[c].rchild; 66 cd[cdlen++] = '1'; 67 } 68 } 69 else{ //當HT[c].weight = 2;
70 HT[c].weight = 0; 71 c = HT[c].parent; //退回到父節點
72 cdlen--; //編碼的長度-1
73 } 74 } 75 }
主函數具體實現:
1 int main(){ 2
3 HuffmanTree HT; 4 HuffmanCode HC; 5
6 int *w, n, i; 7 printf("請輸入權值的個數(>1):"); 8 scanf_s("%d",&n); 9
10 w = (int*)malloc(n*sizeof(int)); 11 printf("請依次輸入%d個權值(整形):\n",n); 12
13 for (i = 0; i <= n - 1;i++) 14 { 15 scanf_s("%d",w+i); 16 } 17 HuffmanCoding(HT, HC, w, n); 18
19 for (i = 1; i <= n;i++) 20 { 21 puts(HC[i]); 22 } 23 return 0; 24 }
全部代碼實現
1 #include<string.h> 2 #include<malloc.h> //malloc()等 3 #include<stdio.h> 4 #include<stdlib.h> 5 #include<ctype.h> 6 #include<limits.h> 7 #include<iostream> 8 9 #define TRUE 1 10 #define FALSE 1 11 #define OK 1 12 #define ERROR 1 13 #define INFEASIBLE -1 14 15 typedef int Status; 16 typedef int Boolean; 17 /************************************************************************/ 18 /* 最優二叉樹簡稱:哈夫曼樹 */ 19 /************************************************************************/ 20 //哈夫曼樹結構 21 ; typedef struct{ 22 unsigned int weight; //權重 23 unsigned int parent, lchild, rchild; //樹的雙親節點,和左右孩子 24 25 }HTNode, *HuffmanTree; 26 27 typedef char** HuffmanCode; 28 29 30 //返回i個節點中權值最小的樹的根節點的序號,供select()調用 31 int Min(HuffmanTree T, int i){ 32 int j, flag; 33 unsigned int k = UINT_MAX; //%d-->UINT_MAX = -1,%u--->非常大的數 34 for (j = 1; j <= i; j++) 35 if (T[j].weight < k && T[j].parent == 0) 36 k = T[j].weight, flag = j; // 37 T[flag].parent = 1; //將parent標志為1避免二次查找 38 39 return flag; //返回 40 } 41 42 void Select(HuffmanTree T, int i,int& s1,int& s2){ 43 //在i個節點中選取2個權值最小的樹的根節點序號,s1為序號較小的那個 44 int j; 45 s1 = Min(T,i); 46 s2 = Min(T,i); 47 if (s1 > s2){ 48 j = s1; 49 s1 = s2; 50 s2 = j; 51 } 52 } 53 54 //HuffmanCode代表的樹解碼二進制值 55 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n){ 56 //w存放n個字符的權值(均>0),構造哈夫曼樹HT,並求出n個字符的哈夫曼編碼HC 57 int m, i, s1, s2, start; 58 unsigned c, f; 59 char* cd; 60 //分配存儲空間 61 HuffmanTree p; 62 if (n <=1) 63 return; 64 //n個字符(葉子節點)有2n-1個樹節點,所以樹節點m 65 m = 2 * n - 1; 66 HT = (HuffmanTree)malloc((m + 1)*sizeof(HTNode)); //0號元素未用 67 //這一步是給哈夫曼樹的葉子節點初始化 68 for (p = HT + 1, i = 1; i <= n; ++i, ++p,++w) 69 { 70 (*p).weight = *w; 71 (*p).lchild = 0; 72 (*p).rchild = 0; 73 (*p).parent = 0; 74 } 75 //這一步是給哈夫曼樹的非葉子節點初始化 76 for (; i <= m; ++i, ++p){ 77 (*p).parent = 0; 78 } 79 /************************************************************************/ 80 /* 做完准備工作后 ,開始建立哈夫曼樹 81 /************************************************************************/ 82 for (i = n + 1; i <= m; i++) 83 { 84 //在HT[1~i-1]中選擇parent=0且weigh最小的節點,其序號分別為s1,s2 85 Select(HT, i - 1, s1, s2); //傳引用 86 HT[s1].parent = HT[s2].parent= i; 87 HT[i].lchild = s1; 88 HT[i].rchild = s2; 89 HT[i].weight = HT[s1].weight + HT[s2].weight; 90 } 91 /************************************************************************/ 92 /* 從葉子到根逆求每個葉子節點的哈夫曼編碼 */ 93 /************************************************************************/ 94 //分配n個字符編碼的頭指針向量,([0]不用) 95 HC = (HuffmanCode)malloc((n + 1)*sizeof(char*)); 96 cd = (char*)malloc(n*sizeof(char)); //分配求編碼的工作空間 97 cd[n - 1] = '\0'; //結束符 98 for (i = 1; i <= n; i++) //每個節點的遍歷 99 { 100 start = n - 1; 101 for (c = i, f = HT[i].parent; f != 0; c = f,f = HT[f].parent){ //每個節點到根節點的遍歷 102 //從葉子節點到根節點的逆序編碼 103 if (HT[f].lchild == c) 104 cd[--start] = '0'; 105 else 106 cd[--start] = '1'; 107 } 108 HC[i] = (char*)malloc((n - start)*sizeof(char)); //生成一個塊內存存儲字符 109 //為第i個字符編碼分配空間 110 strcpy(HC[i], &cd[start]); //從cd賦值字符串到cd 111 } 112 free(cd); //釋放資源 113 } 114 115 //函數聲明 116 int Min(HuffmanTree T, int i); //求i個節點中的最小權重的序列號,並返回 117 void Select(HuffmanTree T, int i, int& s1, int& s2); //從兩個最小權重中選取最小的(左邊)給s1,右邊的給s2 118 void HuffmanCoding(HuffmanTree &HT, HuffmanCode&HC, int* w, int n);//哈夫曼編碼與解碼 119 120 int main(){ 121 122 HuffmanTree HT; 123 HuffmanCode HC; 124 125 int *w, n, i; 126 printf("請輸入權值的個數(>1):"); 127 scanf_s("%d",&n); 128 129 w = (int*)malloc(n*sizeof(int)); 130 printf("請依次輸入%d個權值(整形):\n",n); 131 132 for (i = 0; i <= n - 1;i++) 133 { 134 scanf_s("%d",w+i); 135 } 136 HuffmanCoding(HT, HC, w, n); 137 138 for (i = 1; i <= n;i++) 139 { 140 puts(HC[i]); 141 } 142 return 0; 143 }

問題

參考資料:
《大話數據結構》
《數據結構》算法實現與解析 高一凡著
《數據結構》嚴奶奶
https://www.bilibili.com/video/av35817244?from=search&seid=10785677008277539439
