问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

霍夫曼编码

创作时间:
作者:
@小白创作中心

霍夫曼编码

引用
简书
1.
https://www.jianshu.com/p/e15d23841a73

霍夫曼编码(Huffman Coding)是一种无损的编码算法,在数据压缩和信息传输领域有广泛的应用。它是由戴维·霍夫曼(David Albert Huffman,1925.08.09-1999.10.17)于1952年所发明(发表于论文《A Method for the Construction of Minimum-Redundancy Codes》)。

1951年,正在麻省理工攻读博士学位的霍夫曼收到了他的老师Robert M. Fano教授(给全班学生)布置的一个选择题:或者努力复习准备期末考试,或者交出一份学期论文——找到使用二进制代码表示数字、字母或者其他符号的最佳编码方法——实际上这正是Fano教授自己正在研究的课题。

霍夫曼不想参加考试,于是选择了挑战论文。挑战的过程无疑是痛苦的,不过幸好时间没有那么长——眼看成功无望,霍夫曼决定“悬崖勒马”,好好复习准备期末考试——奇迹在最后一刻发生了。准备将论文草稿扔进垃圾桶的那一刹那,霍夫曼突然间灵光一现,找到了答案。“突然恍然大悟,犹如闪电一般”,霍夫曼回忆那个时刻,如是说到。

于是,霍夫曼提出了以他名字命名的“霍夫曼编码”。那么,霍夫曼编码究竟是解决什么样的问题呢?简单地说,就是任意多段文字(或者数字、或者其他符号),该用什么编码表示,使得其平均编码长度最短。霍夫曼是怎么做的呢?我们以一个简单的例子,来描述霍夫曼编码的算法。

比如,对于字符串“AABCBADAEACCBDB”(记为S1),最常见、最简单的是编码方式ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)。每个字母占用8个比特(A = 0x41 = 01000001,B = 0x42 = 01000010,...),那么S1将占用120个比特(120 = 15 * 8)。不过,这种编码方式,似乎有点浪费。

换个思路,既然S1中其实只包含5个不同的字母(ABCDE),那么实际上可以用3个比特来编码每个字母:A = 000、B = 001、C = 010、D = 011、E = 100。我们将这种编码方式取名A3码(只是随便取一个名字)。那么,按照A3编码,S1将占用45个比特(45 = 15 * 3)。虽然比ASCII码要好很多,不过这似乎这并不是最短的,而且也没有任何通用性可言。比如,对于S2 = “ABCDEFGHI”,此编码方式就失效了(因为按照这种方式,3个比特无法表达9个不同的字母)。

ASCII码和A3码,都是定长码——每个字母所占用的比特数是相同的(ASCII = 8,A3 = 3),如果换个思路,改成变长码呢?比如这样编码(取名V码):A = 0(1个比特)、B = 1(1个比特)、C = 01(2个比特)、D = 10(2个比特)、E = 11(2个比特)。按照V码,S1将占用21个比特(21 = 5 * 1 + 4 * 1 + 3 * 2 + 2 * 2 + 1 * 2)。从编码长度来说,这看起来非常不错,但是V码却有个致命问题,那就是歧义性,如图所示。

通过图可以看到,由于歧义性,V码显然不是一种合适的编码方式。不过,饶是如此,它的“变长”编码方式,却有着霍夫曼编码的影子。霍夫曼编码的精髓,正是“变长”+“无歧义”(当然,还包括“无损”)。而且为了使(平均)编码长度最短,霍夫曼编码使得出现频率最高的字母使用最短编码、出现频率最低的字母使用最长编码。

第1步,霍夫曼编码统计各个字母的出现频率(出现次数),然后按照频率大小,从小到大排列。对于S1,霍夫曼编码的统计排序结果,如表所示。

第2步,霍夫曼编码开始构建二叉树。选取最小频率的两个节点(1:E)、(2:D),并将其频率相加,作为父节点,如图所示。

第3步,将新生成的父节点与原来的节点合并,组成新的霍夫曼编码统计排序表,如表所示。

表中,新生的节点命名为“3”,同时它的“频率”也设置为3。另外,由于E、D已经构建为霍夫曼二叉树的叶子节点,所以,从霍夫曼表中删除。

接下来,重复第2步,得到的霍夫曼二叉树,如图所示。

重复第3步,得到的霍夫曼表,如表所示。

注意,表中新生成的节点“6”(“3”节点和C节点的父节点),按照频率顺序,排在了最后一列。

接下来,继续重复第2步、第3步,所生成的霍夫曼树和霍夫曼表,分别如图和表所示。

仍然是继续重复第2步、第3步,所生成的霍夫曼树和霍夫曼表,分别如图和表所示。


通过表可以看到,只剩下一个节点,那么迭代结束。

迭代结束以后,将图的边,打上标记,左为“0”,右为“1”,如图所示。

按照图,则字母ABCDE就可以得到霍夫曼编码:从根节点开始,到达对应叶子节点的所有路径上“标记”组合,如图和表所示。


通过图或表可以看到,任何一个字母的编码,都不会是另一个字母编码的前缀。这种编码也称为前缀编码(Prefix Encoding)。但是这种称呼,非常令人迷惑:明明不是人家的前缀,偏偏要叫前缀编码。为了消除迷惑呢,这种编码有时候也被称为无前缀编码(Prefix-free Encoding)。迷惑是消除了,但是更加混乱了——前缀编码和非前缀编码,说的竟然是同一回事。

好吧,不纠结其他称呼了,只须记住“霍夫曼编码”这个名字就好,其算法如伪码所示。

# 伪码4-1 霍夫曼编码算法
S = 待编码对象 # 比如字符集,等等
HuffmanTable = 统计并且按照出现频率排序(S) # 比如[E:1,D:2,C:3,B:4,A:5]
HuffmanTree = [] #霍夫曼二叉树,初始化为空
# 构建霍夫曼树
while(True)
{
    if (HuffmanTable.size() <= 1)
    {
        break;
    }
    # 构建霍夫曼树
    node1, node2 = 从HuffmanTable中选取频率最小的前两个元素
    p.value = node1.value + node2.value # 父节点
    p.left_son = node1
    p.right_son = node2
    p.left_edge = 0
    p.right_edge = 1
    HuffmanTree.add(p)
    HuffmanTree.root = p
    # 更新霍夫曼表
    HuffmanTabel.remove(node1, node2)
    HuffmanTable.add&sort(p)
}

# 构建霍夫曼编码
遍历霍夫曼树(HuffmanTree)
针对每个叶子节点,得到霍夫曼编码
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号