快速地:
首先,确保您已经构建了一个规范的霍夫曼代码,其中较短的代码在数字上位于较长的代码之前。这很容易通过首先将您的霍夫曼代码描述为每个符号的位数来轻松完成。然后将霍夫曼代码分配给符号顺序中的最短代码,然后按符号顺序分配下一个最短代码,依此类推。例如
Symbol Bits
A 2
B 4
C 3
D 3
E 2
F 3
G 4
按位排序,保留按符号排序:
Symbol Bits
A 2
E 2
C 3
D 3
F 3
B 4
G 4
分配霍夫曼码,从零开始:
Symbol Bits Code
A 2 00
E 2 01
C 3 100
D 3 101
F 3 110
B 4 1110
G 4 1111
这种规范的方法提供了一种将霍夫曼代码从压缩器传输到解压缩器的紧凑方法,因为您不必发送实际代码或树——只需发送每个符号的代码长度。然后可以在另一端像上面那样构建代码。
现在我们创建解码表、符号表Symbol[] = "AECDFBG"
和代码索引表:
Bits Start Index
2 0000 (0) 0
3 1000 (8) 2
4 1110 (14) 5
现在要解码,您可以从 2 位循环到 4 位,并查看您的代码是否小于下一位大小的起始代码。我们从流中提取四个位并调用它nyb
(如果流中没有四个位,只需附加零位以填充它)。if
在使用's 而不是循环的伪代码中,>>
意味着向下移位:
if nyb < Start[Bits are 3] (= 8) then
output Symbol[Index[Bits are 2] (= 0) + (nyb - Start[Bits are 2] (= 0)) >> 2]
remove top two bits from bitstream
else if nyb < Start[Bits are 4] (= 14) then
output Symbol[Index[Bits are 3] (= 2) + (nyb - Start[Bits are 3] (= 8)) >> 1]
remove top three bits from bitstream
else (must be four bits)
output Symbol[Index[Bits are 4] (= 5) + (nyb - Start[Bits are 4] (= 14))]
remove top four bits from bitstream
应该很容易看出如何把它变成一个循环,从最短的代码长度到第二长的代码长度,如果你没有找到它,它一定是最长的代码长度。
快点:
建立一个长度为2**(最长代码长度)的查找表。表中的每个条目都包含代码中的位数和结果符号。您将比特流的那么多位用作索引。同样,如果比特流没有剩下那么多比特,则用零填充。然后您只需从该索引条目中输出符号并从比特流中删除该索引条目中的位数(这可能小于您为索引提取的位数 - 确保将未使用的位留在比特流)。重复,现在您从剩余的比特流中提取第一个未使用的比特。
在下一个复杂级别,您可以做zlib所做的事情。如果最长的代码比较长(在zlib中最多可以达到15位),与下面的方法相比,您制作表格所花费的时间可能无法支付解码节省的时间。有一个两级表,其中第一级表最多包含小于最长代码的n
位。n
(在 zlib 中,最佳选择n == 9
是 15 位代码。)然后,如果代码是n
位或更少,则表条目提供符号和位数,然后按上述方法进行操作。如果代码多于n
位,那么您将转到一个子表n
-处理剩余位的位值,同样如上。该表条目指示要为子表提取多少位,并定义该子表的大小,称为它k
。您从流中删除最高n
位并提取下一位k
并将其用作子表的索引。然后你得到代码中的符号和剩余位数,并像在单级表中一样继续。请注意,这n+k
不一定是每个子表的最长代码的长度,因为该子表可能只涵盖较短的代码。只有最后一个或几个子表n+k
的长度等于最长代码的长度。
这可能非常快,因为通过构建霍夫曼代码,更短的代码更有可能。大多数时候你会在第一级获得符号,只是偶尔需要进入第二级。主表和所有子表中要填写的表项总数可能远小于覆盖整个代码长度的大表中的表项数。然后减少了准备解码所花费的时间。
如果您有更长的霍夫曼代码(例如 32 位),您可以有更多级别的子表。确定子表的最佳断点需要进行一些实验,这取决于发送新代码的频率以及必须构建表的频率。