哈夫曼編碼是典型的貪心問題,不斷地通過局部最優,最終計算全域最優結果。
1951年,霍夫曼和他在MIT資訊理論的同學需要選擇是完成學期報告還是期末考試。導師Robert
M. Fano給他們的學期報告的題目是,尋找最有效二進位編碼。由於無法證明哪個已有編碼是最有效,霍夫曼放棄對已有編碼的研究,轉向新的探索,最終發現了基於有序頻率二叉樹編碼的想法,並很快證明了這個方法是最有效。
由於這個演算法,學生終於青出於藍,超過了他那曾經和資訊理論創立者克勞德·香農共同研究過類似編碼的導師。霍夫曼使用自底向上的方法構建二叉樹,避免了次優演算法Shannon-Fano編碼的最大弊端──自頂向下構建樹。
為每個符號建立一個葉子節點,並加上其相應的發生頻率
- 當有一個以上的節點存在時,進行下列迴圈:
- 把這些節點作為帶權值的二叉樹的根節點,左右子樹為空白
- 選擇兩棵根結點權值最小的樹作為左右子樹構造一棵新的二叉樹,且至新的二叉樹的根結點的權值為其左右子樹上根結點的權值之和。
- 把權值最小的兩個根節點移除
- 將新的二叉樹排入佇列中.
- 最後剩下的節點暨為根節點,此時二叉樹已經完成。
樣本
Huffman Algorithm
-
| 符號 |
A |
B |
C |
D |
E |
| 計數 |
15 |
7 |
6 |
6 |
5 |
| 機率 |
0.38461538 |
0.17948718 |
0.15384615 |
0.15384615 |
0.12820513 |
在這種情況下,D,E的最低頻率和分配分別為0和1,分組結合機率的0.28205128。現在最低的一雙是B和C,所以他們就分配0和1組合結合機率的0.33333333在一起。這使得BC和DE所以0和1的前面加上他們的代碼和它們結合的機率最低。然後離開只是一個和BCDE,其中有首碼分別為0和1,然後結合。這使我們與一個單一的節點,我們的演算法是完整的。
可得A代碼的代碼長度是1位元,其餘字元是3位元。
-
| 字元 |
A |
B |
C |
D |
E |
| 代碼 |
0 |
100 |
101 |
110 |
111 |
Entropy: 哈夫曼演算法實現
//參考sophia_qing的priotity_queue做法#include <iostream>#include <queue>#include <vector>#include <map>#include <iterator>#include <string>using namespace std;#define Nchar 8 //using 8 bit to describe all symbols#define Nsymbols 1<<Nchar //can describe 256 symbols totally (include a-z, A-Z)typedef vector<bool> Huff_code;map<char, Huff_code> Huff_dic;class Hnode {public:char data;float fre;Hnode *left, *right;Hnode(){data='\0'; fre=0; left=NULL; right=NULL;}Hnode(char c, float f, Hnode* l, Hnode* r){data=c; fre=f; left=l; right=r;}~Hnode(){delete left; delete right;}bool isLeaf(){ return !left&&!right;}};//只能為類類型或枚舉類型的運算元定義重載操作符;//在把操作符聲明為類的成員時,至少有一個類或枚舉類型的參數按照值或者引用的方式傳遞;//因此不能在class Hnode內直接重載;(prepare for pointer sorting);class compare_node{public:bool operator () (Hnode* n1, Hnode* n2 ){return n1->fre > n2->fre;}};Hnode* bulidHtree(int *frequency){priority_queue<Hnode*, vector<Hnode*>, compare_node> Qtree;for(int i=0; i<Nsymbols; i++){if(frequency[i])Qtree.push(new Hnode( (char) i, frequency[i], NULL, NULL));}while(Qtree.size()>1){//不斷定義Hnode* Hnode* lc = Qtree.top();Qtree.pop();Hnode* rc =Qtree.top();Qtree.pop();Hnode* parents = new Hnode((char) 256, lc->fre + rc->fre, lc,rc);Qtree.push(parents);//最後一次是把根節點push進Qtree中}return Qtree.top();//最後返回的是Haffuman樹的根節點}void Huffman_coding(Hnode* root, Huff_code& curcode){if (root->isLeaf()){Huff_dic[root->data]=curcode;return;}Huff_code lcode = curcode;//定義左孩子的編碼;Huff_code rcode = curcode;//定義右孩子的編碼;lcode.push_back(false);rcode.push_back(true);Huffman_coding(root->left, lcode);Huffman_coding(root->right, rcode);}int main(){int freq[Nsymbols] = {0};char* c = "compress this is the string into Huffman code";while(*c != '\0')freq[*c++]++ ;Hnode *root = bulidHtree(freq);Huff_code nullcode;nullcode.clear();Huffman_coding(root, nullcode);for(map<char, Huff_code>::iterator it = Huff_dic.begin(); it!=Huff_dic.end(); it++){cout<<(*it).first<<'\t';copy(it->second.begin(), it->second.end(), ostream_iterator<bool>(cout));cout<<endl;}}
Referance:
1. http://blog.csdn.net/abcjennifer/article/details/8020695
2. http://www.binaryessence.com/dct/en000042.htm3. http://zh.wikipedia.org/zh/%E9%A6%99%E5%86%9C-%E8%8C%83%E8%AF%BA%E7%BC%96%E7%A0%81#.E5.93.88.E5.A4.AB.E6.9B.BC.E7.AE.97.E6.B3.954. http://www.stringology.org/DataCompression/sh/index_en.html