将位串转换为字节(霍夫曼编码)

时间:2012-09-03 00:46:19

标签: python performance algorithm compression huffman-code

在解压缩的霍夫曼编码中,您必须将比特流与多个值(前缀空闲)进行比较。我试图在python中实现一个霍夫曼编码器解码器,这是我将比特流转换为ascii值的代码。

c = ''
l = 0
x = 1
stime = time.time()
while l<len(string):
    if string[l:l+x] in table:
        c+=table[string[l:l+x]]
        l+=x
        x = 1
    else:
        x+=1

我该怎样做才能使这个循环更有效率?

2 个答案:

答案 0 :(得分:1)

如果您准备花费更多时间来构建表,您可以更快地进行解码,因为您可以构建一组表,以便一次处理一个字节的比特流,而不必移位或屏蔽用于挑选单个位的输入流。

您希望构建一组表,以便解码字节流如下所示:

state = 0
for (input in inputBytes)
  output += outputTable[state][input]
  state = stateTable[state][input]

此处输出将是ascii值的可变长度字符串。状态必须记住前一个字节中尚未转换为输出数据的所有信息。构建这些表的一种方法是使状态0成为初始状态 - 当您即将读取输入数据的第一个字节时。然后,对于每个字节,尽可能多地解码并使用它来生成outputTable [0] [byte]。现在查看字节末尾的所有未使用位字符串。对于每个这些字符串,您需要分配一个新状态,并且您需要针对所有可能的字节为每个状态执行相同类型的表构建。当你这样做时,你也会在解码后得到未使用的字符串。如果这些是位串,你已经分配了状态,你可以忽略它们并继续。如果没有,您需要分配更多状态。最终,您将构建表格以应对所有可能的状态。

答案 1 :(得分:1)

快速:

首先,请确保您已经构建了规范的霍夫曼代码,其中较短的代码在较长的代码之前以数字形式出现。通过首先将霍夫曼代码简单地描述为每个符号的位数,可以轻松完成此操作。然后按符号顺序将霍夫曼代码分配给最短代码,然后按符号顺序分配下一个最短代码,依此类推。 E.g。

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代替循环的伪代码中,>>表示向下移位:

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中,对于15位代码,最佳选择结果为n == 9。)然后,如果代码为n位或更少,则表条目提供符号和位数,以及你按上述方式行事。如果代码超过n位,那么您将转到该n的子表 - 处理剩余位的位值,如上所述。该表条目指示要为子表提取多少位,并定义该子表的大小,将其称为k。您从流中删除顶部n位并拉出下一个k位并将其用作子表的索引。然后,您将获得代码中的符号和剩余位数,并按照单级表进行操作。请注意,n+k不一定是每个子表的最长代码的长度,因为该子表可能只包含较短的代码。只有最后一个或几个子表的n+k等于最长代码的长度。

这可能非常快,因为通过构造霍夫曼代码,更短的代码更有可能。大多数情况下,你会在第一级获得符号,并且偶尔必须进入第二级。要填充在主表和所有子表中的表条目总数可以远小于覆盖整个代码长度的大表中的条目数。然后减少准备解码所花费的时间。

如果你有更长的霍夫曼码(例如32位),你可以拥有更多级别的子表。需要进行一些实验来确定子表的最佳断点,这取决于发送新代码的频率和必须构建的表。