Algorithm 文本压缩:编码前缀(标题)以提高性能的策略

Algorithm 文本压缩:编码前缀(标题)以提高性能的策略,algorithm,text,binary,compression,huffman-code,Algorithm,Text,Binary,Compression,Huffman Code,我目前正在做一个哈夫曼文本压缩练习,但是我在标题的编码方面遇到了一些问题。我使用字符频率表将我需要的信息保存为文件解压缩的标题(所有内容都转换为二进制字符串,然后保存在字节数组中) 首先,我用2个字节表示每个字符,1个字节表示字符,1个字节表示频率。然而,我意识到它不适用于某些字符频率可能超过255(1字节)的大文本 因此,我进行了修改,根据每个字符的频率调整其保留字节。它看起来像这样: public String freString(int freq, String s){ freq

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

首先,我用2个字节表示每个字符,1个字节表示字符,1个字节表示频率。然而,我意识到它不适用于某些字符频率可能超过255(1字节)的大文本

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

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;


}
// 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]++;
    }
}
在解压过程中,我将查看下一个字节的值,如果是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)

这是我的问题。。。是否有更好的策略可以用来动态增加字符的保留字节,同时指示“freq字符串的结尾”?我考虑过每个字符至少保留3个字节(1个用于字符,1个用于其频率,1个表示频率字符串的结束),但这会影响较小文本文件的压缩比。
这是我必须做的交易吗?或者有更好的方法吗?

如果您有一个哈夫曼树,那么您可以通过交换任何节点的左、右子节点,创建许多其他哈夫曼树,将相同的长度分配给所有符号,但不同的码字。所有这些树都同样好——它们压缩数据的程度相同,因为长度保持不变。规范哈夫曼是一种事先同意如何从所有可能的排列树中选择一棵特定树的方法,这样您就不必告知您实际使用的是哪棵树

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

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

  • 最长的代码是全零
  • 较短的代码(在右侧用零填充,长度与最长的代码相同)在数字上大于所有较长的代码
  • 具有相同长度的符号按升序分配代码
  • 规则1和2使树的一边最深,另一边最浅,中间没有奇怪的跳跃。规则3对处于相同深度的节点进行排序

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

    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;
    
    
    }
    
    // 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]++;
        }
    }
    
    //创建长度直方图
    常量int maxcodelength=15;//15是Deflate的最大代码长度
    uint[]长度\ U计数=新uint[maxcodelength+1];;
    对于(int i=0;i=0;位--)
    {
    代码=(代码+长度\计数[位+1])>>1;
    next_code[位]=代码;
    }
    //为符号指定代码
    uint[]代码=新的uint[256];
    对于(int n=0;n
    这与的第8页上的代码密切相关,但有所不同(移位方向相反,导致全零代码的长度最长,在Deflate中,全零代码的长度最短)

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


    额外的东西

    确保不超过最大长度的一种方法是将所有频率除以2(四舍五入),然后重新创建哈夫曼树,直到不再超过最大长度。还有其他方法可以计算有效的长度集,例如,无需构建树

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