【问题标题】:Storing and reconstruction of Huffman tree霍夫曼树的存储和重建
【发布时间】:2013-02-26 04:22:17
【问题描述】:

脱水霍夫曼树的最佳方法是什么,脱水我的意思是给定一棵霍夫曼树,以及每片叶子中的字符,你如何有效地存储这棵树的结构,然后再重建它。

取下面的树:

---------------garbage------
 -------------/-------\------
 ------------A-------garbage-
 --------------------/-----\-
 -------------------B-------C-

一个想法可能是将符号存储在每个级别,然后使用此信息来重建树。 在这种情况下: A1B2C2。 那么我怎样才能先获得关卡,并将每个关卡与角色相关联。

【问题讨论】:

  • 你试过什么?你在哪里卡住?构建霍夫曼树在计算上非常便宜,因此以特殊格式存储几乎没有意义。只需存储您首先用于构建树的排序列表。
  • 我使用它来进行压缩,因此需要将足够的信息存储在压缩文件中,以便以后使用这些信息来重建树并解压缩。如果我使用排序列表,它将占用 256*4 字节,但我正在寻找一种方法来节省压缩文件中的更多空间。一个想法是遍历树并将每个符号与其在树上的级别相关联,我正在研究这个想法,但我在实现上陷入困境。
  • 或者如果我重新构建问题,我如何遍历树,存储级别,并将该级别的叶子符号与该级别相关联。

标签: c++ tree huffman-code


【解决方案1】:

您几乎可以肯定不需要存储树本身。你可以这样做,它不应该占用你认为的空间,但通常没有必要。

如果您的霍夫曼编码是规范的,您只需存储每个符号的位长,因为这是生成规范编码所需的所有信息。这是每个符号相对较少的位数,因此应该相当紧凑。您还可以进一步压缩该信息(参见Aki Suihkonen 中的answer)。

自然地,代码的位长与树的深度基本相同,所以我认为这大致就是您要问的问题。重要的部分是知道如何构建规范代码,给定长度 - 它不一定与遍历树产生的代码相同。您可以从中重新生成 a 树,但它不一定是您开始使用的树 - 但通常除了首先确定代码长度之外,您不需要树。

生成规范代码的算法相当简单:

  1. 获取要为其生成代码的所有符号,首先按代码长度(最短的优先)排序,然后按符号本身。
  2. 以零长度代码开头。
  3. 如果下一个符号需要的位数比代码中的当前位数多,请在代码的右侧(最低有效位)添加零,直到长度合适。
  4. 将代码与当前符号相关联,并递增代码。
  5. 循环回到 (3),直到生成所有符号。

获取字符串“香蕉”。显然使用了 3 个符号,“b”、“a”和“n”,计数分别为 1、3 和 2。

所以树可能看起来像这样:

* / \ * 一种 / \ b n

天真地,那可以给出代码:

a = 1 b = 00 n = 01

但是,如果您只是简单地使用位长作为规范代码生成的输入,您会产生这样的结果:

a = 0 b = 10 n = 11

它是一个不同的代码,但显然它会产生相同长度的压缩输出。此外,您只需要存储代码长度即可重现代码。

所以你只需要存储一个序列:

0... 1 2 0... 2 0...

其中“...”表示易于压缩的重复,并且值都非常小(可能每个只有 4 位 - 请注意根本不存储符号)。这种表示将非常紧凑。

如果您确实必须存储树本身,一种技术是遍历树并存储单个位以指示节点是内部节点还是叶节点,然后对于叶节点,存储符号代码。这对于不包含所有符号的树来说是相当紧凑的,即使对于相当完整的树来说也不算太糟糕。最坏情况的大小将是所有符号的总大小,加上尽可能多的单个位节点。对于标准的 8 位字节流,这将是 320 字节(代码为 256 字节,树结构本身为 511 位)。

方法是从根节点开始,针对每个节点:

  • 如果节点是父节点,则输出 0,然后输出左子节点,然后输出右子节点。
  • 如果节点是叶子,输出1,然后输出符号

要进行重构,请执行类似的递归过程,但显然是读取数据并根据需要选择是递归创建子代还是读取符号。

对于上面的示例,树的比特流将类似于:

0, 0, 1, 'b', 1, 'n', 1, 'a'

这是树的 5 位,加上符号的 3 个字节,四舍五入到 4 个字节的存储空间。但是,随着您添加更多符号,它会迅速增长,而存储代码长度则不会。

【讨论】:

  • 这也是在 zlib 中完成的,其中位长本身使用静态 Huf​​fman 树进一步压缩。不幸的是,从 zlib/inflate 规范重写代码并不是一件容易的事。
  • 编码和解码过程是一项简单的任务。要点是使压缩文件尽可能小。压缩文件有编码符号的序列,但它还需要存储它用于编码的树,以便以后解码代码。有时这在空间方面会很昂贵,因为压缩的目的是节省空间。我正在寻找一种聪明的方法来使压缩文件中的树脱水。我的假设是,按照您建议的存储树的方式,在最坏的情况下需要 1024 个字节。
  • 是的,但是代码应该被解码以重建原始文件。这只有在您使用编码时使用的同一棵树时才有可能。
  • 没有。您需要相同的代码,而不是相同的树。显然,压缩和解压缩都需要使用规范编码,但树只需要生成初始代码长度,这就是您需要存储的全部内容。
  • 我试图详细说明这两种技术。我要强调的是,第一种方法是构建规范代码而不是存储树,几乎可以肯定是更好的方法。
【解决方案2】:

zlib specification 解释说,要存储 Huffman 树,只需要每个符号的位长度。例如。如果为 A=101、B=111、C=110、D=01 构造一棵树,则只需计算位长并根据长度重新生成树,以便关键字是连续的 --> A=101,B =110,C=111,D=01。 (或以下代码产生的任何内容)

设置bl_count[2]=1, bl_count[3]=3 并迭代:

code = 0;   // From z-lib specification, RFC 1951
bl_count[0] = 0;
for (bits = 1; bits <= MAX_BITS; bits++) {
    code = (code + bl_count[bits-1]) << 1;
    next_code[bits] = code;
}

由于最大符号长度将小于 16,因此每个符号最多需要 4 位来存储这些长度:3、3、3、2 == 0011 0011 0011 0010;然而,zlib/deflate 做得更好——它运行长度编码这些符号使用转义符号,例如 16 == run of 3、17: run of 4 等,以进一步压缩符号长度流. RLE 也采用零长度的情况,即缺少字符。

【讨论】:

  • 我不明白这是怎么可能的,因为文件的内容(符号)不会产生唯一的 Huffman 树(单个信息源可以生成多个 Huffman 树),并且实际上取决于实现,例如,如果树是用 0child 作为左孩子而不是 0child 作为右孩子来构建的
  • zlib 文档介绍了一种算法,可以将任何有效的霍夫曼树转换为具有完全相同的压缩能力的 canonical 树,即使这意味着要编码“A “必须输出“100110”而不是原始符号“110100”。规范树可从符号长度重现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-27
  • 2010-10-20
  • 2022-06-12
  • 1970-01-01
  • 2014-03-18
  • 1970-01-01
相关资源
最近更新 更多