给定一个文本中出现的一组字符C,每个字符有其出现频率freq,想构造字符的最优二进制表示,即用来编码整个文本的二进制位最少
定长编码:每个字符用相同长度的二进制位数进行编码,则每个字符的长度n必须满足,2^n = |C|
变长编码:思想是赋予高频字符短码字,赋予低频字符长码字
编码过程相对简单,将表示每个字符的码字连接起来即可完成文件压缩
解码过程,如果采用的定长编码则直接解码,变长编码可能产生歧义比较麻烦;但又一种变长编码是无歧义的——前缀吗
前缀码,即没有任何码字是其他码字的前缀;所以编码文件的开始码字是无歧义的
文件的最优编码方案总是对应一棵满(full)二叉树,即每个非叶节点都有两个孩子节点【证明很简单,反正即可,假设一棵非满二叉树是最优编码方案,取其不拥有两个孩子节点的非叶节点,分情况,如果它没有孩子节点,则取以其兄弟节点为根的的树的叶子节点为其孩子节点,这种编码方案更优,因此矛盾;如果它有左孩子节点或者右孩子节点,则取其存在的孩子节点上的叶子节点作为另外一个孩子节点,这种编码方案更有,也矛盾,因此得证】
构造霍夫曼编码
C是字符集合,有n个字符,出现频率分别为c.freq;算法自底向上的构造出对应最优编码的二叉树。
从|C|个叶节点开始,执行|C|-1个“合并”操作创建出最终的二叉树
使用一个以属性freq为关键字最小的最优优先队列Q,从而识别最低频率的两个字符将其合并;当合并两个字符时,得到的新节点的频率设置两者频率之和
HUFFMAN(C)
n = |C|
Q = C //Q是最小优先队列,假定使用最小二叉堆实现,则时间复杂度为O(n)
for i = 1 to n-1 //自底向上提取频率最小的两个节点组成新的节点
allocate a new node z
z.left = x = EXTRACT-MIN(Q)
z.right = y = EXTRACT-MIN(Q)
z.freq = x.freq + y.freq
INSERT(Q, z)
return EXTRACT-MIN(Q) //最后只剩下根节点
霍夫曼编码的思想并不难,也容易理解;较难的地方在于证明其正确性【满足贪心选择性质和最优子结构性质】和编程实现
贪心选择性质:x和y是C中频率最低的两个字符,则存在C的一个最优前缀编码,x和y的码字长度相同,且只有最后一个二进制位不同
最优子结构性质:x和y是C中频率最低的两个字符,C去掉字符x和y,加入一个新字符z得到C',z.freq = x.freq +
y.freq;T'是C'的一个最优前缀码对应的编码树,则将T'中叶节点z替换为一个以x和y为孩子的内部结点,得到树T,T表示字母表C的一个最优前缀码
在这里证明第二个性质,基于第一个
B(T)表示按T进行编码需要多少二进制位
首先B(T) = B(T') + x.freq + y.freq(因为x和y的深度都比z多1)
反证法:假设T对应的前缀码不是最优前缀码,T''才是,则B(T'') < B(T)
由性质一,T''包含兄弟节点x和y,令T'''为T''中将x/y及他们的父结点替换为叶节点z得到的树,则
B(T''') = B(T'') - x.freq - y.freq < B(T) - x.freq - y.freq = B(T')
与T'是最优前缀码矛盾,因此得证。