目录
问题描述
给定一个由字母表 Σ = { a 1 , a 2 , . . . , a n } \Sigma=\{a_1,a_2,...,a_n\} Σ={a1,a2,...,an}组成的文件,每个字母 a i a_i ai对应一个出现频率 f i f_i fi,求霍夫曼编码方案,使得编码后的文件长度最小。为简化问题(使得编对应为一棵树),这里暂定编码必须为前缀码。编码后文件的长度可由 ∑ i = 1 n l i f i \sum\limits_{i=1}^{n}l_if_i i=1∑nlifi来衡量,其中 l i l_i li为字母 a i a_i ai对应的编码长度。
霍夫曼编码问题与最短外部带权路径长度和问题的等价性
2-ary的(仅由01编码的)前缀码对应的编码树是一棵二叉树,一个字母就是一个叶子节点。叶子节点的深度-1就是对应字母的编码的长度,而叶子节点的值就是对应字母的频率。因此求编码后文件的长度就是求编码树的外部带权路径长度(external weighted path length,叶子节点的带权路径长度)和。因此,求使得编码后的文件长度最小的霍夫曼编码方案的问题就转化为求编码树最短外部带权路径长度和问题的问题。
贪心算法
按照出现频次进行贪心,使得出现频率越低的字母对应的码字越长,即处于二叉树的最低层。方法是:
- 选择两个出现频率最低的字母 x x x, y y y,组成一棵子树的叶节点,子树的节点 z z z的频率设为 f z = f x + f y f_z=f_x+f_y fz=fx+fy,从字母表中删去 x , y x,y x,y,向字母表中加入 z z z;
- 重复以上过程,直到字母表中只有剩一个字母。
一个例子如下:
具体编程实现时,可使用一个优先队列来表示这样的二叉树(即一个小顶堆)。
贪心算法正确性证明
需要证明算法求得的外部带权路径长度是最短的。我们记字母序列 { p i } \{p_i\} {pi}为叶子节点形成的一棵树 T T T的外部带权路径长度和 ∑ i = 1 n p i l i \sum\limits_{i=1}^{n}p_il_i i=1∑npili 为 L ( p ; T ) L(p;T) L(p;T) ,记字母序列 { p i } \{p_i\} {pi}的最优方案的值为 L ∗ ( p ) L^*(p) L∗(p)。
由于编码算法比较特殊,不太适合用交换论证法(也可行但麻烦),这里用数学归纳法来证明:
-
假设所有字母已按频率降序排序: p 1 ≥ p 2 ≥ ⋯ ≥ p n p_1\geq p_2\geq\dots\geq p_n p1≥p2≥⋯≥pn
-
当 n = 2 n=2 n=2时,使用1个bit即可,求得的二叉树深度也只有2,是最短的;
-
当 n > 2 n>2 n>2时:考虑最优方案一定具有以下性质:
- 最优方案的编码一定符合频率越大、编码长度越小的原则: l 1 ≤ l 2 ≤ . . . ≤ l n l_1\leq l_2\leq...\leq l_n l1≤l2≤...≤ln ;
- 一定存在一个最优方案,记其对应的树为 T T T,其中 p n p_n pn和 p n − 1 p_{n-1} pn−1节点是一棵子树的两个兄弟叶子节点(siblings), l n − 1 = l n l_{n-1}=l_n ln−1=ln。(因为假如 n n n没有兄弟节点,一定可以将 n n n移至其父节点的位置)
从 T T T构建一棵具有 n − 1 n-1 n−1个节点的树 T 1 T_1 T1,字母序列记为 { q i } \{q_i\} {qi}:其前 n − 2 n-2 n−2个节点与 T T T相同,满足 q 1 = p 1 ≥ q 2 = p 2 ≥ ⋯ ≥ q n − 2 = p n − 2 q_1=p_1\geq q_2=p_2\geq\dots\geq q_{n-2}=p_{n-2} q1=p1≥q2=p2≥⋯≥qn−2=pn−2;删去 T T T的最后2个节点;而第 n − 1 n-1 n−1个节点满足 q n − 1 = p n − 1 + p n q_{n-1} = p_{n-1} +p_n qn−1=pn−1+pn。
L ∗ ( p ) = L ( p ; T ) = ( ∑ i = 1 n − 2 p i l i ) + p n − 1 l n − 1 + p n l n = [ ( ∑ i = 1 n − 2 p i l i ) + ( l n − 1 − 1 ) ( p n − 1 + p n ) ] + ( p n − 1 + p n ) = [ ( ∑ i = 1 n − 2 q i l i ) + ( l n − 1 − 1 ) ( p n − 1 + p n ) ] + ( p n − 1 + p n ) = L ( q ; T 1 ) + ( p n − 1 + p n ) L^*(p)=L(p;T)=(\sum\limits_{i=1}^{n-2}p_il_i)+p_{n-1}l_{n-1}+p_nl_n=[(\sum\limits_{i=1}^{n-2}p_il_i)+(l_{n-1}-1)(p_{n-1}+p_n)]+(p_{n-1}+p_n)=[(\sum\limits_{i=1}^{n-2}q_il_i)+(l_{n-1}-1)(p_{n-1}+p_n)]+(p_{n-1}+p_n)=L(q;T_1)+(p_{n-1}+p_n) L∗(p)=L(p;T)=(i=1∑n−2pili)+pn−1ln−1+pnln=[(i=1∑n−2pili)+(ln−1−1)(pn−1+pn)]+(pn−1+pn)=[(i=1∑n−2qili)+(ln−1−1)(pn−1+pn)]+(pn−1+pn)=L(q;T1)+(pn−1+pn)(计算中用到了 T T T的 p n p_n pn和 p n − 1 p_{n-1} pn−1节点是一棵子树的两个兄弟叶子节点,而 q n − 1 q_{n-1} qn−1的深度与 p n p_n pn相同的条件)。我们无法保证树 T 1 T_1 T1的构造是最优的,故 L ∗ ( p ) ≥ L ∗ ( q ) + ( p n − 1 + p n ) L^*(p)\geq L^*(q)+(p_{n-1}+p_n) L∗(p)≥L∗(q)+(pn−1+pn)。
根据归纳假设, n − 1 n-1 n−1的时候命题成立,即按我们的算法构建的 { q i } \{q_i\} {qi}的树 T 1 ′ T_1' T1′的外部带权路径长度和为 L ∗ ( q ) L^*(q) L∗(q)。而按我们的算法构建的 { p i } \{p_i\} {pi}的树 T ′ T' T′即为 T 1 ′ T_1' T1′的基础上将最底层的值为 p n − 1 + p n p_{n-1}+p_n pn−1+pn那个节点替换为 p n − 1 p_{n-1} pn−1和 p n p_n pn两个节点构成的子树。因此 L ( p ; T ′ ) = L ∗ ( q ) + ( p n − 1 + p n ) L(p;T')=L^*(q)+(p_{n-1}+p_n) L(p;T′)=L∗(q)+(pn−1+pn)。又 L ∗ ( p ) ≥ L ∗ ( q ) + ( p n − 1 + p n ) L^*(p)\geq L^*(q)+(p_{n-1}+p_n) L∗(p)≥L∗(q)+(pn−1+pn)。故 L ( p ; T ′ ) L(p;T') L(p;T′)一定是 L ∗ ( p ) L^*(p) L∗(p),是最短的。得证。
附录:相关概念说明
码字
字母表中一个字母对应的编码串就是码字。
唯一可译
唯一可译,或称唯一可解,英文为uniquely-decodable或uniquely-decipherable,定义为对于任意可解码的编码后信息只有一种解码结果。
唯一可译的判断算法——Sardinas–Patterson算法
先引入尾随后缀(dangling suffix)的概念:
对于一个编码表
C
=
{
a
1
=
c
w
1
,
a
2
=
c
w
2
,
.
.
.
,
a
n
=
c
w
n
}
C=\{a_1=cw_1, a_2=cw_2,...,a_n=cw_n\}
C={a1=cw1,a2=cw2,...,an=cwn},它的所有尾随后缀为:
- 若一个码字 c w i cw_i cwi是另一个码字 c w j cw_j cwj的前缀,则 c w j − c w i cw_j-cw_i cwj−cwi为该编码表的尾随后缀;
- 若一个码字 c w i cw_i cwi是一个尾随后缀 d s j ds_j dsj的前缀,则 d s j − c w i ds_j-cw_i dsj−cwi为该编码表的尾随后缀;
- 若一个尾随后缀 d s i ds_i dsi是一个码字 c w j cw_j cwj的前缀,则 c w j − d s i cw_j-ds_i cwj−dsi为该编码表的尾随后缀。
则一个编码表是唯一可译的,当且仅当它存在正好等于一个码字的尾随后缀。
例子:对于 C = { a = 0 , b = 01 , c = 10 } C=\{a=0,b=01,c=10\} C={a=0,b=01,c=10},所有尾随后缀为 { 1 , 0 } \{1,0\} {1,0},而 0 0 0正好是 a a a的码字,因此该编码表不是唯一可译的。比如 010 010 010存在 a c ac ac和 b a ba ba两种解码。
定长码和变长码
- 定长码(fixed-length code):码字的长度都是相同的。 k k k位编码至多表示 2 k 2^k 2k个码字,因此若共有 n n n个字母则码字长度至少是 k = ⌈ log 2 n ⌉ k=\lceil\log_2n\rceil k=⌈log2n⌉。显然,定长码一定是唯一可译的。
- 变长码(variable-length code):有不相同长度的码字。
前缀码(prefix codes)
仅依靠前缀就能解码的编码。
前缀码一定是唯一可译的,而唯一可译的编码不一定是前缀码。例如 C = { a = 0 , b = 01 } C=\{a=0,b=01\} C={a=0,b=01}是唯一可译的,但不是前缀码,因为读到 1 1 1的时候还需要向后读一个字符才能确定是 a a a还是 b b b。因此,前缀码的优越性在于可以无回溯地(或无需提前读地)完成解码过程,这样的解码效率就很高了。前缀码对应的树(或者说解码自动机)一定是一棵树(若仅由01编码,一定是一棵二叉树;若为定长编码,一定是一棵完全树),其中字母是叶子节点。因此前缀码又称即时码、非延长码。
显然,定长码也一定是前缀码。
讨论:放宽为唯一可译码是否可以更优?
在前文求霍夫曼编码的过程中,为了使得编码成为一棵树而便于讨论,我们加强了问题的条件,令编码必须为前缀码。那么,当我们将条件放宽为唯一可解,编码后的文件长度是否可以比前缀码编码的更小呢?
为了解决这个问题,介绍一下克拉夫特不等式(Kraft-McMillan’s inequality):
设
Σ
=
{
a
1
,
a
2
,
.
.
.
,
a
n
}
\Sigma=\{a_1,a_2,...,a_n\}
Σ={a1,a2,...,an},r-ary编码(即编码中用到
r
r
r种字符)的
C
C
C中对应编码的长度为
l
1
,
l
2
,
…
,
l
n
l_1,l_2,\dots,l_n
l1,l2,…,ln,则编码
C
C
C是前缀编码的必要条件是:
∑
i
=
1
n
r
−
l
i
≤
1
\sum\limits_{i=1}^{n}r^{-l_i}\leq 1
i=1∑nr−li≤1
反之,一定存在满足Kraft不等式长度条件的前缀编码。
而编码
C
C
C是唯一可译编码的必要条件也是:
∑
i
=
1
n
r
−
l
i
≤
1
\sum\limits_{i=1}^{n}r^{-l_i}\leq 1
i=1∑nr−li≤1
反之,一定存在满足Kraft不等式长度条件的唯一可译编码。
因此,前缀编码和唯一可译编码的Kraft不等式限定的最小长度条件是一致的,换言之,它们对文件的最大压缩能力是相同的。所以我们的答案是将条件放宽为唯一可解后编码后的文件长度也不会比前缀码编码的最优方案更小。