经过常规的哈夫曼编码以后,我们需要将每个符号对应的码字记录下来,比较容易想到的是按照字母序记录每个字母的编码,这样的好处是字母与码字的映射关系被隐式记录:
假设字母表
A
=
{
a
1
,
a
2
,
a
3
,
a
4
,
a
5
}
\mathcal{A}=\{a_1,a_2,a_3,a_4,a_5\}
A={a1,a2,a3,a4,a5},
P
(
a
1
)
=
P
(
a
3
)
=
0.2
P(a_1)=P(a_3)=0.2
P(a1)=P(a3)=0.2,
P
(
a
2
)
=
0.4
P(a_2)=0.4
P(a2)=0.4,
P
(
a
4
)
=
P
(
a
5
)
=
0.1
P(a_4)=P(a_5)=0.1
P(a4)=P(a5)=0.1,构造的哈夫曼树如下:
每个码字可以由码字的长度后跟码字来表示。因此,编码将存储为
[
2
,
01
,
1
,
1
,
3
,
000
,
4
,
0010
,
4
,
0011
]
[2, 01, 1, 1, 3, 000, 4, 0010, 4, 0011]
[2,01,1,1,3,000,4,0010,4,0011]。这对于小字母表的哈夫曼编码当然是可以管理的,但是当我们的字母表非常大时,它显然会对压缩性能产生影响。
规范哈夫曼编码
规范哈夫曼编码可以大大减少编码的存储需求。在描述如何构建规范哈夫曼编码之前,我们先来看看哈夫曼树的一个属性。考虑下面的一棵哈夫曼树。我们将展示的是,如果我们只从左到右给出树上的码字的长度,就可以重建编码。例如,在这棵树中,长度为 3、4、4、2、1。
为了通过每个码字的长度重新生成对应的编码,我们从深度为 4(最长码字的长度)的树开始。
然后我们从左边开始对树剪枝,以匹配码字的长度。我们首先修剪掉最左边的两个分支,留下深度为 3 的叶子。这对应于第一个码字。接下来的两个码字的长度为 4,所以我们将接下来的两个叶子留在深度为 4 的位置。下一个码字的长度为 2,因此我们剪掉所有较低层的分支,留下深度为 2 的叶子。最后,我们对树的右半部分进行剪枝,得到长度为 1 的码字对应的叶子。
剪枝后的树可以在左分支和右分支上分别填充 0 和 1 来生成编码。从图中我们可以看到码字为 000、0010、0011、01、1。所以,只要知道特定顺序的码字长度就可以重新生成哈夫曼编码。
Deflate 算法
这又带来一个新的问题:我们不知道哪个码字属于哪个字母。规范过程为我们提供了一种生成隐式包含该信息的编码的方法。为了嵌入这些附加信息,我们需要对哈夫曼编码过程进行一些附加约束:
- 给定长度的所有码字都按字典序连续产生,其顺序与它们所代表的符号相同
- 较短的码字按字典顺序排在较长的码字前面
我们可以将这些约束合并到哈夫曼编码过程中,或者我们可以使用常规方法生成哈夫曼编码,并转换为规范哈夫曼编码。使用后一种方法更简单。为此,我们将从设计哈夫曼编码开始。从这个设计中,我们将提取码字的长度。我们将使用这些长度和规范约束来设计编码。
还是上面那个例子,字母表 A = { a 1 , a 2 , a 3 , a 4 , a 5 } \mathcal{A}=\{a_1,a_2,a_3,a_4,a_5\} A={a1,a2,a3,a4,a5},哈夫曼码字长度为 { 2 , 1 , 3 , 4 , 4 } \{2,1,3,4,4\} {2,1,3,4,4}。我们从最短到最长的顺序生成码字,记住较短的码字按字典顺序排在较长的码字之前的约束。最短的码字分配给 a 2 a_2 a2。长度为 1 的字典序最小码字为 0,因此 a2 的码字为 0。长度为 2 的码字必须为 1x 形式。只需要一个长度为 2 的代码字,即 a 1 a_1 a1 的码字,因此我们将 10 分配给 a 1 a_1 a1。长度为 3 的代码字现在必须采用 11x 的形式。我们只需要一个长度为 3 的代码字,因此, a 3 a_3 a3 的代码字是 110。有两个长度为 4 的码字, a 4 a_4 a4 和 a 5 a_5 a5。因此, a 4 a_4 a4 的码字是 1110, a 5 a_5 a5 的码字是 1111。现在可以仅通过发送码字长度来获得码字。与前面的编码方式不同,我们确切地知道哪个代码字属于字母表中的哪个字母,这是因为字典序的限制。
按照先长度后字典序的方法从左到右剪枝。通过查看码字长度列表,我们知道最短的是
a
2
a_2
a2 的码字,因此先剪出长度为 1 的叶子节点并为
a
2
a_2
a2 编码,接下来是
a
1
a_1
a1,等等。
a
4
a_4
a4 和
a
5
a_5
a5 的码字长度相同,按照字典序先产生
a
4
a_4
a4 的码字。因此,只需对长度列表进行编码就足以存储哈夫曼编码。对于大小为 5 的字母表,这并不是很大的节省;然而,当字母大小为 256 的数量级时,节省的空间会非常可观。