哈夫曼編碼-譯碼器
此次實驗的注釋解析多加不少---若對小伙伴們有幫助 希望各位麻煩點個關注 多謝
1.哈夫曼樹構造算法為:
(1)由給定的n個權值{w1,w2,…,wn}構造n棵只有根結點的二叉樹,從而得到一個二叉樹森林F={T1,T2,…,Tn}。
(2)在二叉樹森林F中選取根結點的權值最小和次小的兩棵二叉樹作為新的二叉樹的左右子樹構造新的二叉樹,新的二叉樹的根結點權值為左右子樹根結點權值之和。
(3)在二叉樹森林F中刪除作為新二叉樹左右子樹的兩棵二叉樹,將新二叉樹加入到二叉樹森林F中。
(4)重復步驟(2)和(3),當二叉樹森林F中只剩下一棵二叉樹時,這棵二叉樹就是所構造的哈夫曼樹。
2.編程實戰:
[問題描述](設計性實驗)
哈夫曼樹很易求出給定字符集及其概率(或頻度)分布的最優前綴碼。哈夫曼編碼正是一種應用廣泛且非常有效的數據壓縮技術。該技術一般可將數據文件壓縮掉20%至90%,其壓縮效率取決於被壓縮文件的特征。
利用哈夫曼編碼進行通信可以大大提高信道利用率,縮短信息傳輸時,降低傳輸成本。但是,這要求在發送端通過一個編碼系統對待傳送電文須預先編碼,在接收須將傳送來的數據進行譯碼。請自行設計實現一個具有初始化、編碼、譯碼、輸入/輸出等功能的哈夫曼碼的編碼/譯碼系統。並實現以下報文的編碼和譯碼:“this program is my favorite”。
[測試數據]
某通信的電文字符集為26個英文字母及空格字符,其出現頻度如下表所示:
1、設計思想描述
(1) 問題分析。
先對輸入的字符串進行操作
再利用haffman編碼進行存儲轉譯
實現節省傳輸成本
(2) 哈夫曼樹中各結點的結構描述(提示:圖示)。
(3) 哈夫曼樹的存儲結構描述(提示:分析采用順序存儲還是利用鏈式存儲等)。
順序存儲
2、主要算法設計與實現
[要求]
1、利用類模板來實現。
2、類中各成員函數要求給出自行設計的算法步驟描述及主要設計思想。
3、給出類中各成員函數算法設計實現。
4、對自行設計的各算法進行評估(提示:時間復雜度、空間復雜度等)。
答:
Haffman(int weight[], int n); //構造函數 時間復雜度O(n)
Code* HaffmanCode(int n,Code* haffCode,char* c);//將haffman進行編碼 時間復雜度O(n^2)
ps:此處就計算所得的 時間復雜度來看有待改善
void encoding(int n,Code* haffCode); //輸出權值與編碼 時間復雜度O(n)
void decoding(int n,Code* haffCode); //輸出譯文 時間復雜度O(n)
3、代碼實現
結構體構造
HaffNode-結點結構、Code-編碼的數據元素結構
struct HaffNode //哈夫曼樹的結點結構 { int weight; //權值 int flag; //標記 int parent; //雙親結點下標 int leftChild; //左孩子下標 int rightChild; //右孩子下標 }; struct Code //存放哈夫曼編碼的數據元素結構 { int bit[MaxN]; //數組 int start; //編碼的起始下標 int weight; //字符的權值 char data; };
Haffman類構造
#include <iostream> using namespace std; class Haffman { HaffNode *hn;//= new HaffNode[2*n+1]; Code *code;//=new Code[n]; int n; public: Haffman(int weight[], int n); //構造函數 時間復雜度O(n) Code* HaffmanCode(int n,Code* haffCode,char* c);//將haffman進行編碼 時間復雜度O(n^2) void encoding(int n,Code* haffCode); //輸出權值與編碼 時間復雜度O(n) void decoding(int n,Code* haffCode); //輸出譯文 時間復雜度O(n) }; Haffman::Haffman(int weight[], int n) //建立葉結點個數為n權值為weight的哈夫曼樹haffTree { hn= new HaffNode[2*n+1]; int j, m1, m2, x1, x2; //哈夫曼樹haffTree初始化。n個葉結點的哈夫曼樹共有2n-1個結點 for(int i = 0; i < 2 * n - 1 ; i++) { if(i < n) hn[i].weight = weight[i]; else hn[i].weight = 0; hn[i].parent = 0; hn[i].flag = 0; hn[i].leftChild = -1; hn[i].rightChild = -1; } for(int i = 0;i < n-1;i++) { m1 = m2 = MaxValue; x1 = x2 = 0; for(j = 0; j < n+i;j++) { if(hn[j].weight < m1 && hn[j].flag == 0) { m2 = m1; //m2放權值第2小的權值 x2 = x1; //m2放權值第2小的下標 m1 = hn[j].weight; //更新m1 x1 = j; //更新x1,下標 } else if(hn[j].weight < m2 && hn[j].flag == 0) { m2 = hn[j].weight; x2 = j; } } //將找出的兩棵權值最小的子樹合並為一棵子樹 hn[x1].parent = n+i; hn[x2].parent = n+i; hn[x1].flag = 1; hn[x2].flag = 1; hn[n+i].weight =hn[x1].weight+hn[x2].weight; hn[n+i].leftChild = x1; hn[n+i].rightChild = x2; } } //哈夫曼編碼算法如下: Code* Haffman::HaffmanCode(int n,Code* haffCode,char* c) //由n個結點的哈夫曼樹haffTree構造哈夫曼編碼haffCode { int child,parent; code=new Code[n]; //求n個葉結點的哈夫曼編碼 for(int i = 0; i < n; i++) { code->start = n-1; //不等長編碼的最后一位為n-1 code->weight = hn[i].weight; //取得編碼對應權值的字符 child = i; parent = hn[child].parent; //由葉結點向上直到根結點 while(parent != 0) { if(hn[parent].leftChild == child) code->bit[code->start] = 0; //左孩子結點編碼0 else code->bit[code->start] = 1; //右孩子結點編碼1 code->start--; child = parent; parent = hn[child].parent; } //保存葉結點的編碼和不等長編碼的起始位 for(int j = code->start+1; j < n; j++) haffCode[i].bit[j] = code->bit[j]; haffCode[i].start = code->start; haffCode[i].weight = code->weight; //保存編碼對應的權值 haffCode[i].data=c[i]; //存儲原始數據 } return haffCode; } void Haffman::encoding(int n,Code* haffCode) { for(int i = 0; i < n; i++) { cout << "Weight = " << haffCode[i].weight << " Code = "; for(int j = haffCode[i].start+1; j < n; j++) cout << haffCode[i].bit[j]; cout<<endl; } } void Haffman::decoding(int n,Code* haffCode) { for(int i = 0; i < n; i++) cout<<haffCode[i].data; cout<<endl; }
主函數
#include <iostream> #include <stdlib.h> const int MaxValue = 10000; //初始設定的權值最大值 const int MaxBit = 4; //初始設定的最大編碼位數 const int MaxN = 30; //初始設定的最大結點個數 #include"Haffman.h" using namespace std; int main() { int n; //weight[]-權重(頻率)數組 int weight[] = {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}; //a[]-按順序存放各個字母 char a[27]={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; char b[30]; //存放輸入字符串 int c[30]; //存放對應b數組的權值 cout<<"請輸入需要編碼英文的字符串:"; cin.getline(b,30); //將輸入的字符串存儲在b數組中 //將c數組權值與b數組字符串進行對應 for(n=0;b[n]!='\0';n++) { for(int l=0;l<27;l++) { if(a[l]==b[n]) { c[n]=weight[l]; break; } } } if(n > MaxN) { cout << "定義的n越界,修改MaxN! " << endl; exit(0); } //生成haffCode數組用作存儲 編碼后的數據 Code* haffCode=new Code[n]; Haffman hm(c, n); //調用生成Haffman樹 Code* co=hm.HaffmanCode(n,haffCode,b);//得到編碼后數組co cout<<"編碼后:"<<endl; hm.encoding(n,co); //對co進行遍歷輸出 cout<<"---------------------------------------"<<endl; cout<<"譯碼為:"<<endl; hm.decoding(n,co); //對編碼數組操作得到譯碼 return 0; }
結果: