【问题标题】:Text compression: Strategy to code prefix(header) to improve the performance文本压缩:编码前缀(标题)以提高性能的策略
【发布时间】:2014-07-10 03:43:07
【问题描述】:

我目前正在进行哈夫曼文本压缩练习,但我在标题编码方面遇到了一些问题。我使用 字符频率表 将我需要的信息保存为标题用于文件的解压(全部转换成二进制字符串,然后保存在字节数组中)。

所以一开始,我为每个字符使用 2 个字节,为字符使用 1 个字节,为它的频率使用 1 个字节。但是我意识到它不适用于某些字符的频率可能超过 255(1byte) 的大文本。

因此我进行了修改,我根据每个字符的频率调整了保留字节。它看起来像这样:

public String freString(int freq, String s){
    freq = freq - 255;
    s = s + ("11111111");

    if(freq>=255){
        s = freString(freq,s);
    }else{

        String remainFreq=  Integer.toBinaryString(freq);
       //patch the ommited zeros of last byte
       remainFreq= String.format("%8s", remainFreq);
       remainFreq=  tempString.replace(' ', '0');
       s = s + remainFreq;
    }

    return s;


}

这样,在解压过程中,我会查看下一个字节的值,如果是 255,则继续添加下一个字节的值……等等。

我的标题示例:

[ 9 , 141, 3, 142, 255, 33, 143, 255, 255, 2 ]

[我的标头长度 = 9 ,a = 3, b = 288, c = 512]

它运行良好,但随着文本越来越大,它大大降低了我的压缩率。 例如:如果“a”重复 5000 次。我将最多使用 20 字节 来存储我的频率值字符串,而不是 2 字节 (00010011 10001000 = 5000)

所以这是我的问题......我可以使用更好的策略来动态增加字符的保留字节并同时指示“频率字符串的结尾”吗?我虽然为每个字符保留至少 3 个字节(1 个用于 char,1 个用于其频率,1 表示频率字符串的结尾),但这会影响较小文本文件的压缩率。 这是我必须采取的权衡吗?还是有更好的方法?

【问题讨论】:

  • 你设置保存频率了吗?保存符号长度是最常见的(通常需要在空间不大的情况下使用技巧)。
  • 您是否需要标头中的实际频率,或者仅足以重新创建用于编码的树?您只有 256 个(最多)不同的频率,(不出所料)可以放入一个字节;你能把实际频率映射到这个更小的空间吗?
  • @ScottHunter 我不确定...因为要重建与以前完全相同的二叉树,我需要每个字符的确切频率,如果我错了,请纠正我
  • @harold 我不确定你所说的符号长度是什么意思。你能解释一下吗?
  • 每个符号都有一个长度,以位为单位。另请参阅canonical Huffman code。和practical Huffman coding。如果你愿意,我可以在答案中详细解释。如果您只想更有效地存储频率,我建议称重每个字节 - 将第二个字节乘以 256,将第三个字节乘以 65536,依此类推。您还可以为每个字节保留一个位以指示它是否是最后一个(显然会更改权重)。

标签: algorithm text binary compression huffman-code


【解决方案1】:

如果你有一个霍夫曼树,那么你可以通过交换任何节点的左右子节点来创建许多其他霍夫曼树,这些树为所有符号分配相同的长度,但代码字不同。所有这些树都同样好——它们压缩数据的程度一样多,因为长度保持不变。 Canonical Huffman 是一种预先商定如何从所有可能的置换树中选择一棵特定树的方法,这样您就不必说明您实际使用的是哪一棵树。

这在实践中意味着可以仅从长度重建树。 实际上没有必要重建树,但是重建它的能力意味着你已经保留了所有信息。

通常情况下,您可以针对哪棵树是规范树做出不同的选择。您所做的选择对解码技术有一些影响,但这可能超出了这个问题的范围。无论如何,一种选择是置换树使得

  1. 最长的代码全为零
  2. 较短的代码(在右侧用零填充,与最长的代码长度相同)在数值上大于所有较长的代码
  3. 长度相同的符号按升序分配代码

规则 1 和 2 使树的一侧最深,另一侧最浅,中间没有奇怪的跳跃。规则 3 对处于相同深度的节点进行排序。

但事实证明,您不需要进行任何树重组。只需使用分配给每个符号的长度,就可以轻松构建代码,如下所示:

// create histogram of lengths
const int maxcodelength = 15; // 15 is the max code length for Deflate
uint[] length_count = new uint[maxcodelength + 1];;
for (int i = 0; i < symbollengths.Length; i++)
    length_count[symbollengths[i]]++;

// find starting point (lowest code) for each length
uint code = 0;
uint[] next_code = new uint[maxcodelength + 1];
next_code[maxcodelength] = 0;
for (int bits = maxcodelength - 1; bits >= 0; bits--)
{
    code = (code + length_count[bits + 1]) >> 1;
    next_code[bits] = code;
}

// assign codes to symbols
uint[] codes = new uint[256];
for (int n = 0; n < codes.Length; n++)
{
    int len = symbollengths[n];
    if (len != 0)
    {
        codes[n] = next_code[len];
        next_code[len]++;
    }
}

这与rfc1951 (Deflate) 的第 8 页上的代码密切相关,但有所不同(移位相反,导致全零代码的长度最长,在 Deflate 中,全零代码的长度最短长度)。

至于标题,现在每个符号只需要 4 位(如果您还使用 15 的长度限制),每个符号肯定不超过 8 位(超过 256 的代码会有点疯狂)。标题仍然是 128 或 256 个字节(对于 256 个字母表)。您可以改进这一点,例如通过借用 Deflate 的行程编码长度方案。


额外的东西。

保证不超过最大长度的一种方法是将所有频率除以 2(向上取整)并重新创建 Huffman 树,直到不再超过最大长度。还有其他方法可以计算一组有效长度,而无需构建树,例如 package-merge

几乎所有使用霍夫曼编码的压缩格式都限制了长度。它对编码和解码都很重要,主要是解码。对于编码,代码不超过 25 意味着您可以使用 32 位缓冲区并写出字节(这意味着缓冲区中最多可以保留 7 位),而不需要特殊情况来处理向缓冲区添加代码时会溢出的情况.对于解码,短(ish)最大代码长度可以实现简单的单表查找 - 它当时使用maxcodelength 位(“窗口”)进行索引,同时给出窗口中的第一个符号(实际解码)和该符号的长度(因此可以移出)。如果最大代码长度更长,则需要稍微复杂一些的技术,例如多级表,或者我个人最喜欢的,不同的表取决于窗口中前导零的数量。

【讨论】:

  • 非常感谢。我看到了很多有用的信息,我今晚会尝试一下。再次,谢谢!你太棒了!
猜你喜欢
  • 2012-06-30
  • 2010-09-30
  • 2011-07-11
  • 1970-01-01
  • 1970-01-01
  • 2013-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多