If you can not explain it simply,you do not understand it well enouth!
不能简明的解释一件事,说明你对它懂得不多! --爱因斯坦
一、背景
1951年,哈夫曼在麻省理工学院(MIT)攻读博士学位,他和修读信息论课程的同学得选择是完成学期报告还是期末考试。导师罗伯特·法诺(Robert Fano)出的学期报告题目是:查找最有效的二进制编码。由于无法证明哪个已有编码是最有效的,哈夫曼放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证明了这个方法是最有效的。哈夫曼使用自底向上的方法构建二叉树,避免了次优算法香农-范诺编码(Shannon–Fano coding)的最大弊端──自顶向下构建树。
1952年,于论文《一种构建极小多余编码的方法》(A Method for the Construction of Minimum-Redundancy Codes)中发表了这个编码方法。
二、哈夫曼树的基本概念
霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。哈夫曼树的应用很广,哈夫曼编码就是其中一种。
2.1结点间的路径和路径长度
路径:指从一个结点到另一个结点之间的分支序列。
路径长度:是指从一个结点到另一个结点所经过的分支数目。
2.2结点的权和带权路径长度
结点的权:在实际的应用中,给树的每个结点赋予一个具有某种实际意义的实数,称该实数为这个结点的权。
带权路径长度:在树型结构中,把从树根到某一结点的路径长度与该结点的权的乘积,叫做该结点的带权路径长度。
2.3 树的带权路径长度
树的带权路径长度为树中从根到所有叶子结点的各个带权路径长度之和,通常记为:
三、哈夫曼树的构造原理
3.1构造方法
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
3.2构造哈夫曼树举例
(1)8个结点的权值大小如下:
(2)从19,21,2,3,6,7,10,32中选择两个权小结点。选中2,3。同时算出这两个结点的和5。
(3)从19,21,6,7,10,32,5中选出两个权小结点。选中5,6。同时计算出它们的和11。
(4)从19,21,7,10,32,11中选出两个权小结点。选中7,10。同时计算出它们的和17。
(5)从19,21,32,11,17中选出两个权小结点。选中11,17。同时计算出它们的和28。
(6)从19,21,32,28中选出两个权小结点。选中19,21。同时计算出它们的和40。另起一颗二叉树。
(7)从32,28, 40中选出两个权小结点。选中28,32。同时计算出它们的和60。
(8)从 40, 60中选出两个权小结点。选中40,60。同时计算出它们的和100。 好了,此时哈夫曼树已经构建好了。
四、哈夫曼编码
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。
例如,在英文中,e的出现机率最高,而z的出现概率则最低。当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。
在电报通信中,电文是以二进制的0,1序列传送的,在发送端需要将电文中的字符转成二进制的0,1序列(即编码),在接收端又需要将接收到的0,1转换成对应的字符序列(即译码)。
最简单的二进制编码方式是登场编码,若电文中只使用A,B,C,D,E,F这6个字符,若进行等长编码,则需要二进制的三位即可,依次编码为:000,001,010,011,100,101,110。若用这6个字符作为6个叶子节点,生成一颗二叉树,让该二叉树的的左右节点分别用0,1编码,从树根节点到每个叶子节点的路径上所经的0,1编码序列等于该叶子节点的二进制编码,则对应的编码二叉树,如下图:
通常,电文中每个字符出现的频率是不同的,在一份电报中,这个6个字符额出现频率依次为:4,2,6,8,3,2,则电文被编码后的总长度可以有计算出:L=3*(4+2+6+8+3+2)=75;可知,采用等长编码时,传送电文的总长度为75。
那么是否可以缩短传送电文的长度呢?若采用不等长编码,让出现频率高的字符具有较短的编码,让出现频率低的字符具有较长的编码,这样有可能缩短电文的总长度。采用不等长编码要避免译码时的多义性,假设用0表示D,用01表示C,则当接收到编码后的字符串….01…时是按照0对应D译码还是按照01对应C译码,这里就产生了多义性问题。因此对一个字符集进行不等长编码时,则要求字符集中任意一个字符的编码都不能是其他字符的前缀,符合此要求的编码属于无前缀编码。
为了使不等长编码成为无前缀编码,可用该字符集中的每个字符作为叶子节点生成一颗编码二叉树。为了获得电文的最短长度,可以将每个字符出现的频率作为叶子节点的权值,求出此树的最小带权路径长度就等于求出了传送电文的最短长度。
由前面生成哈夫曼编码树的算法可以生成如下:
由哈夫曼树得到的字符编码就称为哈夫曼编码。其中A,B,C,D,E,F这6个字符对应的哈夫曼编码依次为:00,1010,01,11,100,1011。电文最终传送的长度为:
42+24+62+82+33+24=61,比等长编码时的75减少了不少。
五、哈夫曼树构造的算法实现
#include<list>
#include <algorithm>
using namespace std;
typedef struct _HuffumanTree
{
int weight;//权值
_HuffumanTree* left;//左儿子节点
_HuffumanTree* right;//右儿子节点
}HuffumanTree;
HuffumanTree* CreateHTree(int *pArray,int nsize)
{
list<HuffumanTree*>listHf;
//1.根据权值先创建树节点
for (int i = 0;i<nsize; i++)
{
HuffumanTree* phf = (HuffumanTree*)malloc(sizeof(HuffumanTree));
phf->weight = pArray[i];
phf->left = nullptr;
phf->right = nullptr;
listHf.push_back(phf);
}
HuffumanTree* m_leftChild{ nullptr };//左
HuffumanTree* m_rightChild{ nullptr };//右
HuffumanTree* newtree{ nullptr };
while (listHf.size() != 1)
{
listHf.sort( [](HuffumanTree * node1, HuffumanTree * node2) {return node1->weight < node2->weight; });
m_leftChild = listHf.front();
listHf.pop_front();
m_rightChild = listHf.front();
listHf.pop_front();
newtree = (HuffumanTree*)malloc(sizeof(HuffumanTree));
newtree->weight = m_leftChild->weight + m_rightChild->weight;
newtree->left = m_leftChild;
newtree->right = m_rightChild;
listHf.push_back(newtree);
}
return listHf.front();
}
//打印哈夫曼树
void printHfTree(HuffumanTree* root)
{
if (root)
{
cout << root->weight << endl;
if (root->left)
{
cout << root->left->weight << " ";
}
else {
cout << "没有左孩子\n";
}
if (root->right)
{
cout << root->right->weight << " ";
}
else {
cout << "没有右孩子\n";
}
cout << endl;
printHfTree(root->left);
printHfTree(root->right);
}
}
void main()
{
int arrayW[] = { 19, 21, 2, 3, 6,7,10,32 };
HuffumanTree* m_hf = CreateHTree(arrayW,sizeof(arrayW)/sizeof(arrayW[0]));
printHfTree(m_hf);
system("pause");
}
结果: