Huffman 编码压缩算法
前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法。相信大家应该听说过 David Huffman 和他的压缩算法—— Huffman Code,一种通过字符出现频率,Priority Queue,和二叉树来进行的一种压缩算法,这种二叉树又叫Huffman二叉树 —— 一种带权重的树。从学校毕业很长时间的我忘了这个算法,但是网上查了一下,中文社区内好像没有把这个算法说得很清楚的文章,尤其是树的构造,而正好看到一篇国外的文章《A Simple Example of Huffman Code on a String》,其中的例子浅显易懂,相当不错,我就转了过来。注意,我没有对此文完全翻译。
我们直接来看示例,如果我们需要来压缩下面的字符串:
“beep boop beer!”
首先,我们先计算出每个字符出现的次数,我们得到下面这样一张表 :
字符 | 次数 |
‘b’ | 3 |
‘e’ | 4 |
‘p’ | 2 |
‘ ‘ | 2 |
‘o’ | 2 |
‘r’ | 1 |
‘!’ | 1 |
然后,我把把这些东西放到Priority Queue中(用出现的次数据当 priority),我们可以看到,Priority Queue 是以Prioirry排序一个数组,如果Priority一样,会使用出现的次序排序:下面是我们得到的Priority Queue:
接下来就是我们的算法——把这个Priority Queue 转成二叉树。我们始终从queue的头取两个元素来构造一个二叉树(第一个元素是左结点,第二个是右结点),并把这两个元素的priority相加,并放回Priority中(再次注意,这里的Priority就是字符出现的次数),然后,我们得到下面的数据图表:
同样,我们再把前两个取出来,形成一个Priority为2+2=4的结点,然后再放回Priority Queue中 :
继续我们的算法(我们可以看到,这是一种自底向上的建树的过程):
最终我们会得到下面这样一棵二叉树:
此时,我们把这个树的左支编码为0,右支编码为1,这样我们就可以遍历这棵树得到字符的编码,比如:‘b’的编码是 00,’p’的编码是101, ‘r’的编码是1000。我们可以看到出现频率越多的会越在上层,编码也越短,出现频率越少的就越在下层,编码也越长。
最终我们可以得到下面这张编码表:
字符 | 编码 |
‘b’ | 00 |
‘e’ | 11 |
‘p’ | 101 |
‘ ‘ | 011 |
‘o’ | 010 |
‘r’ | 1000 |
‘!’ | 1001 |
这里需要注意一点,当我们encode的时候,我们是按“bit”来encode,decode也是通过bit来完成,比如,如果我们有这样的bitset “1011110111″ 那么其解码后就是 “pepe”。所以,我们需要通过这个二叉树建立我们Huffman编码和解码的字典表。
这里需要注意的一点是,我们的Huffman对各个字符的编码是不会冲突的,也就是说,不会存在某一个编码是另一个编码的前缀,不然的话就会大问题了。因为encode后的编码是没有分隔符的。
于是,对于我们的原始字符串 beep boop beer!
其对就能的二进制为 : 0110 0010 0110 0101 0110 0101 0111 0000 0010 0000 0110 0010 0110 1111 0110 1111 0111 0000 0010 0000 0110 0010 0110 0101 0110 0101 0111 0010 0010 0001
我们的Huffman的编码为: 0011 1110 1011 0001 0010 1010 1100 1111 1000 1001
从上面的例子中,我们可以看到被压缩的比例还是很可观的。
作者给出了源码你可以看看( C99标准) Download the source files
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《Huffman 编码压缩算法》的相关评论
Huffman 是信息理论中理论最优的压缩算法,但是他有很大的缺陷:首先他必须知道各个字符出现的频率;其次他需要一个字符编码表用以解码,事实上,个人认为很多时候加上这个编码表之后,Huffman 编码就可能达不到最优的编码效果了。摒弃Huffman 编码的优缺点不论,Huffman 树也很简单啊,用不着另外去讲吧?
有点类似于上面那位.
哈夫曼上课讲吐了,当年预习就瞬间搞懂了,结果还上课的时候被老师骚扰了N多节课…
还是rsync更值得看.
@Walkerinwind
两位果然厉害,我表示看了足足半个小时才看懂,而且编码的唯一性那里还是不是很理解….
btw,感觉如果是小的程序,例如只有唯一字符的a-z的26个字符的文件中,编码后文件得加上编码表,恐怕比源文件还要大了吧
@g0t3n:
壓縮是需要成本的。小東西拿來壓,可連固定成本都攤不回來。
字典编码,lzw.
http://baike.baidu.com/view/401141.htm
虽然Huffman不算难,也不至于瞬间就懂了吧?
希望楼主有空专门能讲下NP问题
@hoamon
恩恩,我觉得也是… 不知道有没有什么简单的压缩算法实现大家能推荐下呢?
@g0t3n
因为字符都在叶节点上,所以不会存在某一个编码是另一个编码的前缀。
事实上,mindhacks.cn的这篇文章讲得足够清楚。
http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/
我看了两遍,我理解能力不好,很多地方不是看得很明白,主要是太长了。
好像说只要是二叉树就能保证编码的唯一性吧,哈夫曼只是最优的二叉树
@g0t3n
因为所有的字符都是在霍夫曼树的叶节点上的,中间的节点没有,所以不会有一个字符的编码是另一个字符的前缀的情况~
@felix021
看了好久,为什么有那么一点点简单事情复杂化的感觉呢…
@hadesmile
任意二叉树,只要让所有码都是从root到leaf,都可以得到一组唯一的,不会存在某一个编码是另一个编码的前缀的编码.
哈弗曼编码的压缩率是很高 好像接近信息论中可以压缩到的极限
但是他也有很多不足
1.需要预先统计码出现的频率
2.需要保存码表 用于解码
3.错误会传递,即如果一个解码错误,会使得后面的解码错误,而不被知道
编码的过程我看懂了。但是不理解为什么要这么编码:
1. 在我看来就没有压缩啊。直接传ascii码不是一样的吗?
2.唯一性是怎么保证的。
这玩意我在不同的课上学了6遍……这是第7遍……这辈子都忘不了了
@Channe
简单的说就是出现频率越高的字符编码越短,这样就有压缩了。
唯一性上面很多人都说过了,没有任何一个字符编码是另一个字符编码的前缀
@囧泥 都是什么课啊~ 我完全没看出有压缩的效果。
@囧泥
频率越高的字符编码越短,频率越低的字符编码越长。重复率越高压缩效果越好,明白了。
@Channe
既然你这么感兴趣……就是以下6门课
Algorithmic Foundations And Problem Solving
Data Structure
Complexity of Algorithms
Efficient Algorithm
Applied Algorithmics
Information Theory and Coding
嗯……Huffman Coding是它们(唯一)的交集=_=
一直对如何保证唯一性有困惑,以前看过的,忘记了。
前几天是准备写一下huffman code的博文的,没想到你占先了。Huffman Code的核心我觉得就是贪心算法。
想问下您,这种好的英文博客是怎么发现的?
上面有人说“Huffman 是信息理论中理论最优的压缩算法”,但在实际应用中有一个很重要的问——题确定信元的大小。如果盲目认为一个字节就是信元单位,很难达到最优压缩效率。常见的游程编码方法(RLE方法,应用于rar/7z/gif等格式中),采用动态决定信元大小以达到较优的压缩效率。个人认为,Huffman仅仅是“编码方法”而非“压缩算法”,完整的压缩算法应当包含目标数据的描述复杂性分析和编码两部分,而个人认为Huffman在第一点做的不足。
例如:字符串”ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC”
即可以描述为“64个”ABC””,而单纯采用文中所述的以字节为单位的Huffman编码要长于这个描述。
传说中可以把稳定无记忆离散信源压缩到信源墒的霍夫曼压缩算法,实际中几乎毫无用处。因为这样的稳定信源根本就不存在;强行预测信源模型,如果对信源的模型预测偏差较大的话,这个压缩算法性能退化很快。不过,好处就是简单易懂原理清晰明白,所以国内计算机本科生基本上都在课堂里学了无数遍,毕业生绝大多数都说不出来第二种压缩算法(一种也说不出来的也不少,呵呵)。当然对如何推导该算法,如何证明他是某个确定的稳定无记忆离散信源的“最优”压缩算法,基本就不会讲了,所以网上又有很多对他的误解。
@champ
楼上独立发现了多符号编码法增加压缩度的方法。
@champ
同意你的“Huffman仅仅是“编码方法”而非“压缩算法””这个观点。我也认为Huffman Coding只是编码的方式达到了最好的压缩效果(每个bit传递了最多信息),属于有压缩效果的编码方法,而不是专门的压缩算法
貌似在汇编程序设计中也用到了这种思想:常用指令的机器码较短,不常用的较长
@Enoch
LZW压缩效果不是很好,尽管他没有使用额外的码表。我曾经使用lzw压缩bmp文件,有很大概率,压缩后的大小比原文件还大
25楼告诉我们,对于有记忆的离散信源,应该如何扩展使用霍夫曼算法来进行压缩。这里简单的说,就是重新定义符号表以及频率。
30楼告诉我们,对于任意一个压缩算法,如果输入序列集合不受限制,必然存在某些输入序列,压缩输出比压缩结果大。可参见典型序列集等信息论知识。
25楼后面那个“64个ABC”已经不是编码方法而是压缩算法了吧……而且和Huffman Coding没啥关系了……是Run-length encoding的思路了
@zy498420
@囧泥
你没发现他是一个符号组“ABC”频率为100%的输入吗?呵呵
前些天刚学哈夫曼编码,表示二叉树还能这么用,碉堡了啊
综上可见,本文对于某些细节的描述,还稍显欠缺,这也正是楼上几位陷入迷惑的缘故。如果想要深入了解此类无损压缩算法,还是应当先了解一些基础知识,而不是随意的猜测,或者通过“现象“去得到”结论“。
以前上课时写过java的Huffman压缩/解压程序,压缩文本文件(ascii码)时压缩率一般都有50%,但用来压缩pdf体积反而增大了1倍!
@zy498420
用Huffman Coding的话应该是用一个bit代表ABC,然后所有的ABC都换成这一个bit吧。“64个ABC”和这个的思路并不相同吧
不管什么文件,都拿1个字节作为单位去用霍夫曼方法压缩,会出现压缩后大小不减反而增大的情况么(排除压缩后生成的字典表的大小)@champ
@囧泥
兄弟,2种压缩算法,第一种认为,信源是一个相邻3*64”位“都有”关系“的有记忆信源。第二种认为,信源是一个相邻3”位“都有关系的记忆信源。前者结果包含一个1,一个3*64“位”长的码表,总长度是193,后者包含一个64,一个3“位”长的码表,总长度4。兄弟你看明白,霍夫曼算法的本质了吗?
本文其实没有解答几个很关键的问题:为什么数据能够压缩,以及数据无损压缩的极限在哪里(上帝不会那么慷慨的)。楼上几位可以去查查这几个问题的答案,就明白了。
@stevenliu
好问题,有没有想过准备一份英文词典,以词为单位去进行霍夫曼压缩?提醒:“频率”一词,是统计术语,在大数据量的时候可以模拟“概率”,但是不等价哟。
可否解释清楚一些?在下愚笨。如果压缩任何的文件,将1byte看作信源单位,也就是0-255(2^8)一共256个不同的表示,统计这个256个表示的频率然后根据霍夫曼树生成的对应表替换相应的bits,这样压缩出来的数据会不会一定小于源文件(不考虑压缩效率)@zy498420
每个byte的内容都可能不一样,而按照你的说法,似乎只取其中一个来去频率?这显然是不对的,在极端的情况下,似乎会大于原文件。。。比如你取得频率与实际频率恰恰相反。。。然后就是这个是字母的表示吧。。。你不能用二进制的01来算的。。。本身哈夫曼编码就是用01替代字母等数据,01替代01,显然是在做无用功。@stevenliu
同感啊,离散,数据结构,算法..对了,计算机组成原理貌似还顺便讲过这个..
霍夫曼其实就是用01换01,例如上文中 的’b’在ascii编码中是98,对应二进制为01100010,通过统计频率得到了对应bits为00,这里的信源单位其实就是1个byte,因为在ascii码中是用1个byte表示一个字符。不知道这种方法能不能推广到一般的文件类型,既byte里面的数据不是ascii码,而是任意可能的8个bits@枫起
说实话,没看懂。。。其实我也才上周上数据结构的课才学到哈夫曼编码的。。。我理解错了,我以为你说的byte取得是它的二进制表示。。。这样就拿个问题就没了,但是鉴于哈夫曼编码必须要知道频率,你这种只取一个nyte频率的方法显然不可取,而如果全部取出来算频率。。。说实话,似乎得不偿失吧?@stevenliu
Huffman coding這個算法很簡單,但它的證明過程可是相當蛋疼,當時班裡就幾個人聽懂了。。。
前几天刚在sicp上看到这个算法,感觉非常简洁而优美。
ps.解码的过程就是把二进制中的0和1分别对应到在二叉树中向左走和向右走,每到一个叶子节点就提出相应字符,然后重新回到根节点,所以不可能产生前缀问题的
在jpeg看过表头维护一个huffman编码表,受用了
啊,这样啊。所有的有效编码都是在一个空的节点上衍生出来的,而每一个空节点又是从另一个空节点衍生出来的。也就是说所有的节点的前缀编号都标识的是一个空节点,这样就保证了所说的特性。
嗯嗯~有意思。哈哈,菜鸟一个,刚刚开始学习,勿喷。