信息论(三):霍夫曼编码
我们有一组符号,比如字母,每个符号都有出现的概率。我们想给每个符号分配一个二进制代码(由 0 和 1 组成的字符串),使得:常用符号使用短代码,不常用符号使用长代码,并且没有代码是其他代码的前缀,这样解码就不会产生歧义。
这被称为前缀码,霍夫曼算法(由大卫·霍夫曼于 1952 年提出)可以找到最短的前缀码,即平均长度最接近熵极限 H(X) 的前缀码。
霍夫曼编码(Huffman coding)背后的思想:把这些符号想象成一棵棵小树,每棵树代表一个概率。我们将逐步合并概率最小的节点,直到只剩下一棵树。每次合并都会将两个“轻”的分支合并成一个更重的分支,就像先把最不确定的部分编织在一起一样。
假设我们的数据源包含以下概率:A:0.4,B:0.3,C:0.2,D:0.1。
算法过程如下:
选择两个最小的概率:C(0.2) 和 D(0.1),将它们合并成一个新节点,概率为 0.3,C 和 D 成为该节点的子节点。
现在我们得到:A(0.4), B(0.3), CD。
再次选择两个最小的概率:B(0.3) 和 CD,合并,得到新节点 0.6。
现在:A(0.4),BCD。
合并最后两个节点,A(0.4) + BCD,得到根节点 1.0。
完成!我们得到了一棵二叉树。
接下来是分配位,左移 = 0,右移 = 1(反之亦然)。
一种可能的结果:A: 0,B: 10,C: 110,D: 111。
现在我们得到了变长码,常用符号用较短的码,不常用符号用较长的码。
最后,检查平均长度:L = 0.4(1) + 0.3(2) + 0.2(3) + 0.1(3) = 1.9 比特/符号。
该分布的熵:H = -(0.4log₂0.4 + 0.3log₂0.3 + 0.2log₂0.2 + 0.1log₂0.1) ≈ 1.846 比特。
它们几乎相同,霍夫曼编码使我们达到了熵的极限!任何编码都无法做得更好,除非允许小数位,例如算术编码。
直觉来看,每次我们合并最小概率时,我们实际上是在说:“让最罕见的事件共享一个共同的前缀,因为它们很少被使用。” 这就像构建一种语言,其中你最常用的词都很短,“是”、“否”、“爱”。而你很少用到的词却很长,比如“反政教分离主义”(英文:antidisestablishmentarianism)。这就是压缩的诗意。
霍夫曼编码过程:
输入:选择任意文本,一首诗、一段文字,甚至是一首摇篮曲。
1. 统计每个字母或符号出现的次数。
2. 将这些频率转化为概率。
3. 构建霍夫曼树,逐步合并出现频率最低的字母,直到得到你的编码。
4. 使用这些可变长度的二进制字符串对文本进行编码。
5. 最后,比较该编码与固定长度编码(例如 ASCII,每个字符使用 8 位)的总比特数。
输出:频率表,霍夫曼树,每个符号对应的编码,以及最终的压缩比。
通常情况下,霍夫曼编码几乎神奇地更短。其中,频率越不均匀,意味着某些字母出现的频率远高于其他字母,压缩率就越高。
