哈夫曼编码是典型的贪心问题,不断地通过局部最优,最终计算全局最优结果。
1951年,霍夫曼和他在MIT信息论的同学需要选择是完成学期报告还是期末考试。导师Robert M. Fano给他们的学期报告的题目是,查找最有效的二进制编码。由于无法证明哪个已有编码是最有效的,霍夫曼放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证明了这个方法是最有效的。
由于这个算法,学生终于青出于蓝,超过了他那曾经和信息论创立者克劳德·香农共同研究过类似编码的导师。霍夫曼使用自底向上的方法构建二叉树,避免了次优算法Shannon-Fano编码的最大弊端──自顶向下构建树。
为每个符号建立一个叶子节点,并加上其相应的发生频率
- 当有一个以上的节点存在时,进行下列循环:
- 把这些节点作为带权值的二叉树的根节点,左右子树为空
- 选择两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且至新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
- 把权值最小的两个根节点移除
- 将新的二叉树加入队列中.
- 最后剩下的节点暨为根节点,此时二叉树已经完成。
示例
-
符号 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: