【什么是编码】
例:给出一段字符串,它只包含A、B、C、D、E这5种字符。字符出现频率不同,如下表。现对其进行二进制编码,要求无二义性且码文尽可能短。
1.等长编码
最简单的编码方法是把每个字符都用于都用相同长度的二进制数来表示,如下表。
显然无二义性,每个字符用3位二进制数表示,存储的总长度是:3 * (3+9+6+15+19) = 156。
2.变长编码
出现次数多的字符用短码表示,出现少的用长码表示,如下表。
可以看到,此时也没有二义性,例如"1100 111 10 0 1101",解码后唯一得到"ABDEC"。
存储的总长度是:3 * 4 + 9 * 3 + 6 * 4 + 15 * 2 + 19 * 1 = 112。第二种方法相当于对第一种方法,压缩比是:156/112=1.39。
但是,变长编码有其缺陷:胡乱设定编码方案,很可能错误,例如:
存在二义性,编码无法解码还原。例如"100",是"A"、"BE"还是"DEE"呢?
错误的原因是,某个编码是另一个编码的前缀(prefix),即这两个编码有包含关系,导致了混淆。
因此需要寻找一套编码方式, 使得其中任何一个字符的编码都不是另一个字符的编码的前缀,同时把满足这种编码方式的编码称为前缀编码。Huffman编码便是前缀编码算法中的最优算法,是能使给定字符串编码成01串后长度最短的前缀编码。
【哈夫曼树与哈夫曼编码】
1.概念
把叶子结点的权值乘以其路径长度的结果称为这个叶子结点的带权路径长度,其中叶子结点的路径长度是指从根结点出发到达该结点所经过的边数。(例如上面的例子中,权值为 66 的叶子结点的带权路径长度为 6∗2=126∗2=12,而权值为 11 的叶子结点的带权路径长度为 1∗3=31∗3=3
树的带权路径长度 (Weighted Path Length of Tree, WPL) 等于它所有叶子结点的带权路径长度之和。
对同一组叶子结点来说,哈夫曼树可以是不唯一的,但是最小带权路径长度一定是唯一的。
Huffman编码是利用贪心思想构造二叉编码树的算法。如果把A、B、C、D、E的出现次数 (即频数) 作为各自叶子结点的权值,那么字符串编码成01串后的长度实际上就是这棵树的带权路径长度。
2.构造
对所有字符(即叶子结点)按频次排序:
在这里插入图片描述
从最少的字符开始,用贪心思想安排在二叉树上。现把A、C放到二叉树上:
在这里插入图片描述
把B放到二叉树上,调整D:
在这里插入图片描述
把D放到二叉树上,调整E:
把E放到二叉树上,得到最终的哈夫曼树。
将哈夫曼树的左树枝记为0,右树枝记为1,便得到哈夫曼编码,如下表:
通过这个例子也可以发现,对哈夫曼树来说不存在度为1的结点,并且权值越高的结点相对来说越接近根结点。