最優二叉樹(赫夫曼樹)


赫夫曼樹的介紹(寫的不好地方大佬請指教)

最優二叉樹又稱哈夫曼樹,是帶權路徑最短的二叉樹。根據節點的個數,權值的不同,最優二叉樹的形狀也不同。

圖 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 }
View Code

問題

參考資料:

《大話數據結構》

《數據結構》算法實現與解析  高一凡著

《數據結構》嚴奶奶 

https://www.bilibili.com/video/av35817244?from=search&seid=10785677008277539439


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM