赫夫曼编码原理与实现

1951年,Huffman在MIT信息论的同学需要选择是完成学期报告还是期末考试。导师Robert M. Fano给他们的学期报告的题目是,寻找最有效的二进制编码。由于无法证明哪个已有编码是最有效的,Huffman放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证明了这个方法是最有效的。由于这个算法,学生终于青出于蓝,超过了他那曾经和信息论创立者香农共同研究过类似编码的导师。Huffman使用自底向上的方法构建二叉树,避免了次优算法Shannon-Fano编码的最大弊端──自顶向下构建树。

以上是我从百度百科上的摘抄。我们大致可以从中了解赫夫曼编码的历史以及其根本作用——有效地压缩数据。需要多说一句的是,对于Huffman编码的中文译名有大概三种:“哈夫曼”、“霍夫曼”、“赫夫曼”。其实都是同一个人,同一种编码了。本文中,我采用《算法导论》中的译名——赫夫曼。

定长编码与变长编码

好了,言归正传。先看看如果需要对字符型的数据文件编码,在赫夫曼编码产生之前是怎样做的。假设现在有一个10万字符的数据文件,它只出现了6个不同的字符:a,b,c,d,e,f,他们出现的频率,经统计,如下表所示:

a b c d e f
频率(千次) 45 13 12 16 9 5
定长编码 000 001 010 011 100 101
变长编码 0 101 100 111 1101 1100

既然是有6个字符,那么,根据二进制编码的规律,3bit的二进制串就可以把这6个字符完全表示出来,因为3bit的二进制串最多可以表示8个互不相同的项目。表示出来的编码形式就是表中“定长编码”那一行所示。

这么一来,通过计算得知,我们按照这种方法编码文件,一共生成了300000个bit. 再来看看表格最后一行的变长编码,它的基本逻辑是这样:对字符采取不定长的编码方式,其中,对于字符频率越高的字符,用相对较短的编码;而对于字符频率越低的字符,则采用较长的编码。

这样,即便变长编码时,有些字符的编码长度超过了定长编码的情况(比如这里e和f的编码长度都是4,大于3),但总体来说,还是相当节省空间的。比如,表格中的例子,经过计算,变长编码的文件大小是224000bit,比起定长编码,大约节省了1/4.

好了,接下来,进入主题,这种高效的变长编码,也就是赫夫曼编码是如何实现的?

赫夫曼树的构造

首先,补充一个概念——前缀码。它指的是这样一种编码方式:没有任何字符是其他字符的前缀。所以,从语义上理解,好像叫“非前缀码”要更贴切一点,但是这个名字已经是业内标准,也只能这么叫了。赫夫曼编码正是这样一种前缀码。也因为它的这种性质,我们在对赫夫曼编码的文件解码时,不会对首个字符产生歧义,以此类推,就能顺利解码了。

这里,说解码的原因是告诉大家,不用担心这种变长编码的解码问题。

至于如何构造赫夫曼编码,关键就在于构造赫夫曼树。赫夫曼树,是一棵满的二叉树(所谓“满”是指它的任何一个非叶节点都有两个孩子),它采用的是一种从底置顶,自下而上的构建方式。具体如下:

(1) 拿上面表格中的数据为例,我们现将所有字符根据其频率按升序排序:我在这里干脆写成了Python中字典的形式,方便观察。
{'f': 5, 'e': 9, 'c': 12, 'b': 13, 'd': 16, 'a': 45}

(2) 为这个排好序的字典的每个条目,生成一个树节点,树节点类这样构造:

class HuffmanTreeNode
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值