脱水霍夫曼树的最佳方法是什么,脱水我的意思是给出一个霍夫曼树,每个叶子中的字符,你如何有效地存储这棵树的结构,然后重建它。
采取以下树:
---------------garbage------
-------------/-------\------
------------A-------garbage-
--------------------/-----\-
-------------------B-------C-
一个想法可能是在每个级别存储符号,然后使用此信息重建树。 在这种情况下: A1B2C2。 那么我怎样才能首先获得关卡,并将每个关卡与角色联系起来。
答案 0 :(得分:4)
您几乎肯定不需要存储树本身。你可以做到,它不应该占用你认为它所做的空间,但通常不是必需的。
如果您的霍夫曼代码是规范的,则只需存储每个符号的位长度,因为这是生成规范编码所需的所有信息。这是每个符号相对较少的位数,因此应该相当紧凑。您还可以进一步压缩该信息(请参阅answer中的Aki Suihkonen)。
当然,代码的位长度与树深度基本相同,所以我认为这大致是你所要求的。重要的部分是知道如何构建规范代码,给定长度 - 它不一定与遍历树所产生的代码相同。您可以从此重新生成 a 树,但它不一定是您开始使用的树 - 但通常您不需要树而不是首先确定代码长度。
生成规范代码的算法非常简单:
取字符串“banana”。显然,使用了3个符号,'b','a'和'n',分别为1,3和2。
所以树可能看起来像这样:
* / \ * a / \ 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, 0, 1, 'b', 1, 'n', 1, 'a'
树的5位,加上符号的3个字节,最多可存储4个字节。但是,当您添加更多符号时,它会迅速增长,而存储代码长度则不会。
答案 1 :(得分:2)
zlib specification解释了存储霍夫曼树只需要每个符号的比特长度。例如。如果为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 ==运行3,17:运行4等,以进一步压缩符号长度流。此外,RLE采用零长度的情况,即缺少字符。