C++哈夫曼樹編碼和譯碼的實現


一.背景介紹:

  給定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 }
哈夫曼編碼譯碼

五.運行截圖:

                  

                  

                  

 


免責聲明!

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



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