假设有一组字符{a,b,c,d,e,f},对应的频率分别为5%、9%、12%、13%、16%、45%。请问以下哪个选项是字符a,b,c,d,e,f分别对应的一组哈夫曼编码?( )
A. 1111,1110,101,100,110,0
B. 1010,1001,1000,011,010,00
C. 000,001,010,011,10,11
D. 1010,1011,110,111,00,01
【答案】
A
【解析】
哈夫曼编码(Huffman Coding)是为实现数据压缩而设计出来的一种编码方法,它的压缩办法其实很简单:字符出现的频率越高,其编码长度越短。
这个不难理解。假设我们要写一部小说,里面有个人物叫“混元一气上方太乙金仙美猴王齐天大圣斗战胜佛孙悟空”,出场十万八千次,还有一个叫“乌巢卵二金刚独角青牛大鬼王”的小妖怪,只出场一次。如果将前者的名字简化为“大圣”、“悟空”、“WK”、“monkey”、M、1(代表金箍棒),我们就能大幅减少书的厚度;而后者的名字再怎么缩短也起不到啥作用。
可惜的是,我们单凭这个规律无法锁定答案,因为4个选项全都满足。
假设你只知道这个规律,现在让你盲猜一波,你会选择哪个答案?
我想你也多半会选A。为什么?最后一个字符f出现的频率足足有45%,几近半壁江山,分量自然是其他字符不可同日而语。什么能代表它的分量呢?编码长度。因此它的编码长度应该要比别的字符都要小。干就干到极致,只有一位数才是最短的。这样一番推理我们就成功蒙对了答案。
毛估估一下5个字符的频率分步,基本上可以分成3个级别:
①个位级:5%、9%。
②十余级:12%、13%、16%。
③半壁江山级:45%。
对照答案A,正分别对应2个4位编码,3个的3位编码,1个1位编码,这特性还不够明显吗?
这正是哈夫曼编码追求的目标:什么级别的频率对应什么级别的编码。
这套分级编码体系是怎么打造出来的呢?
方法就是根据频率大小把所有字符拼成一颗树,这颗树的特别之处是从树梢开始向树根拼(从下向上拼),总是将两个还没拼的最小的节点拼成一个较大的节点,拼成的树叫“哈夫曼树”。
因为只是比较大小,所以为了方便,可以字符频率都扩大一百倍,得到a:5、b:9、c:12、d:13、e:16、f:45。
下面开始编码:
1.拼树
记住拼树的要点:每次都从没拼过的节点中找频率最小的两个拼。
(1)一开始所有节点都没拼过,最小的节点为a、b,拼成新节点f1,频率是a、b的和14。
(2)现在没拼过的节点从小到大为:c:12、d:13、f1:14、e:16、f:45,最小的节点为c、d,拼成新节点f2,频率25。
(3)现在没拼过的节点从小到大为:f1:14、e:16、f2:25、f:45,最小的节点为f1、e,拼成新节点f3,频率30。
(4)现在没拼过的节点从小到大为:f2:25、f3:30、f:45,最小的节点为f2、f3,拼成新节点f4,频率55。
现在你可能发现一个规律,就是每次拼接时都将频率较小的节点放在左边,频率较大的节点放在右边。这不是强制规定,但是习惯上都这么做。
(4)现在没拼过的节点从小到大为:f:45、f155,拼成新节点f5,频率100。
(5)只剩下一个没拼过的节点了,想拼也拼不成了,拼树大功告成。
哈夫曼树说到底就是一颗“排辈”树,你可以将5个字符看成是5个人,字符的频率看成是人的年龄,哈夫曼树就是给这5个人按年龄排辈份。
2.编码
一般约定每个左分支编码为“0”,右分支编码为“1”。编码如下:
编码规则:从根节点到叶子节点路径上的字符组成的字符串为该叶子节点的哈夫曼编码。
按上图求出a,b,c,d,e,f的哈夫曼编码为:
1100,1101,100,101,111,0
费了半天劲,得出的结果咋和题中所给选项都不一样呢?
这是因为哈夫曼编码并不唯一。在整个拼树过程中,咱们一直遵守两个规则:
①要拼的两个节点,频率较小的放左边,频率较大放右边。
②每个左分支编码为“0”,右分支编码为“1”。
不过这两个规则并不是强制性的,所以哈夫曼编码不唯一。咱们只要把几个1和0互换一下(下图红色字体部分),就可以得到和选项A一样的哈夫曼编码。
虽然编码不一样,但每个字符的编码长度确是一定的,即长度依次为:4、4、3、3、3、1,根据这个特点也可以快速确定答案为A。
【题目来源】
2023 CCF非专业级别软件能力认证第一轮 (CSP-J1) 入门级C++语言试题 第10题