【问题标题】:Decoding Huffman file from canonical form从规范形式解码霍夫曼文件
【发布时间】:2015-04-11 07:30:04
【问题描述】:

我正在编写一个 Huffman 文件,其中我将规范代码的代码长度存储在文件的标题中。在解码过程中,我能够重新生成规范代码并将它们存储到 std::map<std:uint8_it, std::vector<bool>> 中。实际数据被读入单个std::vector<bool>。在有人建议我使用std::bitset 之前,让我澄清一下霍夫曼代码具有可变位长,因此,我使用的是std::vector<bool>。那么,鉴于我有我的符号及其相应的规范代码,我该如何解码我的文件?我不知道从这里去哪里。有人可以向我解释一下我将如何解码这个文件,因为我在搜索时找不到与它相关的任何内容。

【问题讨论】:

  • Generally speaking, the process of decompression is simply a matter of translating the stream of prefix codes to individual byte values, usually by traversing the Huffman tree node by node as each bit is read from the input stream—Huffman coding.

标签: huffman-code canonicalization


【解决方案1】:

您无需创建代码或树即可解码规范代码。您所需要的只是按顺序排列的符号列表以及每个代码长度中的符号数。 “按顺序”是指按码长从最短到最长排序,在每个码长内,按符号值排序。

由于代码长度内的规范代码是连续的二进制整数,您可以简单地进行整数比较以查看您拥有的位是否在该代码范围内,如果是,则进行整数减法以确定它是哪个符号。

下面是来自puff.c 的代码(稍作更改),以明确显示这是如何完成的。 bits(s, 1) 从流中返回下一位。 (假设总是有下一位。)h->count[len] 是按长度编码的符号数量len 代码,其中len 在0..MAXBITS 中。如果将h->count[1]、h->count[2]、...、h->count[MAXBITS] 相加,即为编码符号的总数,即为h->symbol[] 数组的长度。 h->symbol[] 中的第一个 h->count[1] 符号长度为 1。h->symbol[] 中的下一个 h->count[2] 符号长度为 2。以此类推。

h->count[] 数组中的值(如果正确)被限制为不会超额订阅可在len 位中编码的可能代码数量。可以进一步限制它表示完整的代码,即没有未定义的位序列,在这种情况下decode() 不能返回错误 (-1)。要使代码完整且不会超额订阅,h->count[len] << (MAXBITS - len) 与所有 len 的总和必须等于 1 << MAXBITS。

简单的例子:如果我们用一位编码e,用两位编码t,用三位编码a和o,那么h->count[]就是{0, 1, 1, 2}(第一个值,@ 987654347@ 未使用),而h->symbol[] 是{'e','t','a','o'}。那么e的代码是0,t的代码是10,a是110,o是111。

#define MAXBITS 15              /* maximum bits in a code */

struct huffman {
    short *count;       /* number of symbols of each length */
    short *symbol;      /* canonically ordered symbols */
};

int decode(struct state *s, const struct huffman *h)
{
    int len;            /* current number of bits in code */
    int code;           /* len bits being decoded */
    int first;          /* first code of length len */
    int count;          /* number of codes of length len */
    int index;          /* index of first code of length len in symbol table */

    code = first = index = 0;
    for (len = 1; len <= MAXBITS; len++) {
        code |= bits(s, 1);             /* get next bit */
        count = h->count[len];
        if (code - count < first)       /* if length len, return symbol */
            return h->symbol[index + (code - first)];
        index += count;                 /* else update for next length */
        first += count;
        first <<= 1;
        code <<= 1;
    }
    return -1;                          /* ran out of codes */
}

【讨论】:

  • 嗨,马克...我真的很感谢你花时间写出来。但是,我很抱歉地说我无法理解其中的大部分内容。这不是你的解释,但也许是我的理解水平让我很难。无论如何...感谢您的解释,马克。 +1
  • 只需通过示例逐步完成例程,您就会明白。没有比这更简单的了。尝试从左到右解码这些位:10111100 提供的示例代码。
  • 好的...谢谢,我会这样做的。不过,有几件事。您从代码示例中省略了 struct state。我也应该声明吗?那究竟是做什么的?而且,我将如何使用这个函数解码给定的位?该函数接受struct huffman 的参数,出于上述原因,我正在将文件中的位读入std::vector&lt;bool&gt;。对不起,如果我的问题看起来很愚蠢或愚蠢,但我实际上并没有通过阅读它来理解该函数的作用。
  • 对于这个例程,状态只是提供了如何以及从哪里获取位。您可以将bits(s, 1) 替换为您想要从流中返回下一位的任何值,一个等于0 或1 的整数。对于我在上面的评论中提供的示例位,bits(s, 1) 的第一次调用将返回 1。第二次调用将返回0,第三次调用将返回1。以此类推。
  • @TemplateRex 这些位是向后的,因此您需要将它们反转,这是一次位代码正在执行的操作。反过来可以用一个表来完成,但是一旦你去那里,你也可以使用这个表来解码。然后你会得到类似 zlib 的膨胀。
【解决方案2】:

您的地图包含相关信息,但它将符号映射到代码。 但是,您尝试解码的数据包含代码。 因此,您的地图不能用于获取与以有效方式读取的代码相对应的符号,因为查找方法需要一个符号。搜索代码并检索相应的符号将是线性搜索。

相反,您应该重建为压缩步骤构建的 Huffman 树。 内部节点的频率值在这里无关紧要,但您需要叶节点位于正确的位置。 您可以在读取文件头时动态创建树。最初创建一棵空树。对于您阅读的每个符号到代码的映射,在树中创建相应的节点。 例如。如果符号“D”已映射到代码 101,则确保根有一个右子节点,该节点有一个左子节点,该左子节点有一个包含符号“D”的右子节点,创建节点是否丢失。

使用该树,您可以按如下方式对流进行解码(伪代码,假设取右孩子对应于在代码中添加 1):

// use a node variable to remember the position in the tree while reading bits
node n = tree.root
while(stream not fully read) {
    read next bit into boolean b
    if (b == true) {
        n = n.rightChild
    } else {
        n = n.leftChild
    }
    // check whether we are in a leaf node now
    if (n.leftChild == null && n.rightChild == null) {
        // n is a leaf node, thus we have read a complete code
        // add the corresponding symbol to the decoded output
        decoded.add(n.getSymbol())
        // reset the search
        n = tree.root
    }
}

请注意,反转地图以使查找进入正确方向仍会导致性能欠佳(与二叉树遍历相比),因为它无法像遍历那样利用对较小搜索空间的限制。

【讨论】:

  • 感谢您的回复,muued。不过,我确实有些怀疑。 decoded.add(n.getSymbol())... 那就是我将生成的代码与地图中现有的代码匹配并将其添加到文件中的地方,对吧?我正在使用std::back_inserter 进行添加。另外,如何检查叶节点? node == NULL,对吧?最后,您提到反转地图......我将在哪里反转地图?请告诉我这个。谢谢。
  • 我试图通过编辑进一步解释它。 decoded.add(n.getSymbol()) 是您成功将读取的代码匹配到符号并将其附加到输出文件的位置。叶检查是显式执行的。您可以反转地图以获得从代码到符号的映射。这个方向是您想要的方向,因为您阅读了代码并想要相应的符号。但是,树遍历是更好的方法。
  • 好的...现在我很清楚这一点。我将尝试实现这一点,看看它是如何进行的。然而,最后一件事。由于我在压缩步骤中使用频率构建我的树,而且我没有将频率写入文件,而且你提到频率值是不相关的,我将如何仅使用我的地图重新创建树?我试图搜索这个但找不到任何东西。请告诉我这个。我真的很感谢你的帮助。谢谢。
  • 您可以使用您的地图,或者(甚至更简单)在您阅读文件时使用文件中的信息,从而完全节省地图的创建时间。我解释了如何在另一个编辑中创建树。如您所见,您只需创建节点并将符号放入叶子中。不需要频率。
  • 好的...我想我可以使用它。我将尝试实现这一点,看看会发生什么。如果我遇到任何问题,我会再次在这里发帖。非常感谢您的帮助,muued。
猜你喜欢
  • 1970-01-01
  • 2023-03-22
  • 2023-03-14
  • 2014-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多