二叉树-赫夫曼编码
我们日常使用压缩和解压软件的频率可谓是非常高,而最基本的压缩算法 —— 赫夫曼编码,其中使用的二叉树就是赫夫曼树。在介绍赫夫曼编码之前,我们先来介绍赫夫曼树。
什么是赫夫曼树
我们通过一个例子来引入什么是赫夫曼树。
我们在中小学每年期末考试结束后都会领到成绩单,成绩单上列出我们考试分数的等级,比如优秀(>=90)、良好(>=80)、合格(>=60)、不合格(<60),以某个班为例,总人数是100人,不同成绩区间的比例如下:
不考虑效率因素的话,我们可以这么实现成绩等级判定:
这种情况下,比例占80%以上的分数都需要经历三次以上的判断才能得到结果,显然不合理,对此,我们可以对判断逻辑进行改进:
大部分同学的成绩都在 80 分左右,因此我们判断的逻辑改成了先通过 80 分对成绩进行划分,看起来效率是提升了,这其中的原理是每个分数都要循环调用此逻辑,将 80 分放到入口判断,总体判断次数更少,效率更高。
这就是赫夫曼树的雏形。显然,上述流程图可以抽象为一棵二叉树,每个等级的人数看作路径权重,我们把带权路径长度(权重x路径求和)最小的树叫做赫夫曼树,把上面两个流程图抽象为带路径权重的二叉树如下:
二叉树 a
的带权路径长度是 1*5+2*15+3*40+4*30+4*10 = 315
,二叉树 b
的带权路径长度是 2*40+3*5+15*3+2*30+10*2 = 220
,这意味着,对于人数 100 的班级,通过第一种方式要做 315 次比较,对于第二种方式,只需要 220 次比较,显然二叉树 b 比二叉树 a 更优。
赫夫曼树的构建
当然,上述二叉树 b 还不是赫夫曼树,因为它不是最优的,赫夫曼树的构造方式如下:
- 把有权值的节点按照从小到大顺序进行排序:A5、E10、B15、D30、C40;
- 取最小的两个节点作为某个新节点 N1 的子节点,较小的作为左子结点;
- 然后将 N1 替代 A5 和 E10,插入上述有序序列,保持从小到大排序:N115、B15、D30、C40;
- 重复步骤2,将 N1 和 B 作为一个新节点 N2 的左右节点,依次类推,直到把所有节点纳入树中。
最终形成的二叉树如下所示:
对应的带权路径长度是 140+230+315+45+4*10 = 205,对于 100 个学生而言,需要进行 205 次判断。比前面的二叉树 b 更优,同时也是最优的二叉树,所以是赫夫曼树。
压缩算法的基础: 赫夫曼编码及其应用
当然,赫夫曼不会闲到为了转化下成绩等级专门实现赫夫曼树,当年,他研究赫夫曼树是为了解决远距离通信(主要是电报)数据传输的最优问题。
比如,我们需要在网络上传输 BADCADEEFD
字符串序列给其他人,每个字符占一个字节,如果要压缩的话可以通过二进制编码的方式进行传输,这个字符串包含了 6 个字符:ABCDEF,我们可以用对应的二进制表示如下:
字符 A B C D E F
二进制 000 001 010 011 100 101
这样,真正传输的数字编码就是 001000011010000011100100101011,对方接收时按照 3 位一分来译码,如果文章很长,这个序列串也会非常长。
而事实上,不管是英文还是中文,不同字母或汉字的出现频率是不同的,我们能否参考上篇文章按照不同成绩区间分布概率不同构建赫夫曼树的方式将这里的字符编码进行优化呢?
答案是可以,这种通过赫夫曼树对字符数据进行编码的方式就叫做赫夫曼编码。
具体实现方式如下:
上述 BADCADEEFD 中不同字符的出现大致概率是这样的:
字符 A B C D E F
概率 20% 10% 10% 30% 20% 10%
合起来是 100%,我们可以这样来构建赫夫曼树(按照上面赫夫曼树构建规则构建):
然后我们将权值左分支改为 0,右分支改为 1,对应的赫夫曼树如下:
我们按照这六个字母经过路径的权值对字母进行重新编码:
字符 A B C D E F
新编码 11 100 101 01 001 000
这样一来我们就得到了新的字符编码,这就是赫夫曼编码,我们通过赫夫曼编码对字符串 BADCADEEFD 进行重新编码:
- 原编码二进制串:001000011010000011100100101011(30个字符)
- 新编码二进制串:1001101101110100100100001(25个字符)
这样一来,我们的数据被压缩了,节省了约 17% 的传输成本,随着字符串长度增加,重复字符增多,效果更明显,并且重复字符越多,压缩效果越好。
最后,在接收方如何解码呢?赫夫曼编码的结果是不同字符编码长短不一,很容易混淆,这就要求发送方和接收方约定好同样的编码规则,就好比二战时期,我们情报人员都要保有同样的密码本一样。