前言
哈夫曼编码是一种贪心算法和二叉树结合的字符编码方式,具有广泛的应用背景,最直观的是文件压缩。本文主要讲述如何用哈夫曼编解码实现文件的压缩和解压,并给出代码实现。
哈夫曼编码的概念
哈夫曼树又称作最优树,是一种带权路径长度最短的树,而通过哈夫曼树构造出的编码方式称作哈夫曼编码。
也就是说哈夫曼编码是一个通过哈夫曼树进行的一种编码,一般情况下,以字符 “0” 与 “1” 表示。编码的实现过程很简单,只要实现哈夫曼树,通过遍历哈夫曼树,这里我们从根节点开始向下遍历,如果
下个节点是左孩子,则在字符串后面追加 “0”,如果为其右孩子,则在字符串后追加 “1”。结束条件为当前节点为叶子节点,得到的字符串就是叶子节点对应的字符的编码。
哈夫曼树实现
根据贪心算法的思想实现,把字符出现频率较多的字符用稍微短一点的编码,而出现频率较少的字符用稍微长一点的编码。哈夫曼树就是按照这种思想实现,下面将举例分析创建哈夫曼树的具体过程。
下面表格的每一行分别对应字符及出现频率,根据这些信息就能创建一棵哈夫曼树。
字符
出现频率
编码
总二进制位数
a
500
1
500
b
250
01
500
c
120
001
360
d
60
0001
240
e
30
00001
150
f
20
00000
100
如下图,将每个字符看作一个节点,将带有频率的字符全部放到优先队列中,每次从队列中取频率最小的两个节点 a 和 b(这里频率最小的 a 作为左子树),然后新建一个节点R,把节点设置为两个节点
的频率之和,然后把这个新节点R作为节点A和B的父亲节点。最后再把R放到优先队列中。重复这个过程,直到队列中只有一个元素,即为哈夫曼树的根节点。
由上分析可得,哈夫曼编码的需要的总二进制位数为 500 + 500 + 360 + 240 + 150 + 100 = 1850。上面的例子如果用等长的编码对字符进行压缩,实现起来更简单,6 个字符必须要 3 位二进制位表示,
解压缩的时候每次从文本中读取 3 位二进制码就能翻译成对应的字符,如 000,001,010,011,100,101 分别表示 a,b,c,d,e,f。则需要总的二进制位数为 (500 + 250 + 120 + 60 + 30 + 20)*
3 = 2940。对比非常明显哈夫曼编码需要的总二进制位数比等长编码需要的要少很很多,这里的压缩率为 1850 / 2940 = 62%。哈夫曼编码的压缩率通常在 20% ~90% 之间。
下面代码是借助标准库的优先队列 std::priority_queque 实现哈夫曼树的代码简单实现,构造函数需要接受 afMap 入参,huffmanCode 函数是对象的唯一对外方法,哈夫曼编码的结果会写在 codeMap 里
面。这部分是创建哈夫曼树的核心代码,为方便调试,我还实现了打印二叉树树形结构的功能,这里就补贴代码,有兴趣的同学可以到文末给出的 github 仓库中下载。
1 using uchar = unsigned char;2
3 structNode {4 uchar c;5 intfreq;6 Node *left;7 Node *right;8 Node(uchar _c, int f, Node *l = nullptr, Node *r =nullptr)9 : c(_c), freq(f), left(l), right(r) {}10 bool operator
11 retur