深入解析数据压缩算法
前序
开始本文之前,先回顾一下上篇。上篇讲解了几种数据压缩算法中的两种:Huffman压缩算法和RLE压缩算法。
详解数据压缩算法(上):http://blog.csdn.net/fengchaokobe/article/details/7934865
正文
本文将详解数据压缩算法的后两种算法:Rice压缩算法、LZW压缩算法。
第一节 LZW压缩算法
LZW压缩算法:Lempel-Ziv-Welch Encoding。算法的命名简单直接,故而LZW压缩算法也跟随了算法命名的特性:简单易懂!---有点扯皮了,开始干正事!
LZW压缩算法的实现就是通过在编码的过程中建立一个字符串表,并用某个数字来表示这个串,压缩文件只存储那个数字。这些数字就是压缩后的数据。
LZW算法的实现原理:由于源数据字符串中一定会出现重复的字符串,于是我们就利用这点,将第一次出现的串组合用某一数字表示,然后将这个数字保存起来。那么当再次遇见这个串时就可直接用这个数字来表示,以此来达到压缩的目的。所以说,能正确的得到这个数字表是压缩成功的关键。
从原理中我们可得知:在源数据中,如果重复的字串越多,那么压缩的效果就越好。
好了,理论说的再多也是空谈,既然了解了,那我们就上例子来说明。
现有如下的源数据串,我们现用LZW压缩算法来操作实现压缩:
战前准备:
1.原理中提到我们用某一数字来表示压缩的结果,那么这些数字的起始怎么选择呢?这时我们就要根据LZW压缩算法的应用来说了。LZW主要应用在图像的处理上,如果图像的色彩数为256,那我们就要从258开始(其中256表示清除码,257表示图像结束码)。
2.编码过程中一些声明:
源数据:压缩的目标数据;
索引数组:编码后字符串表所对应数字表的匹配数组。
匹配结果:Yes | No。
编码字符表:经过编码得到的结果。
数字表标号:当有新的编码生成时会得到相应的标号。
万事俱备,进入状态:
编码过程中须遵守如下的规则:
编码的过程:
从表中我们可知:编码字符表中的字符和数字就是我们LZW编码的结果,我们只需将字符串表和数字表一一对应即可,最后我们只保留数字串。
好了,这就是LZW压缩算法的编码过程。不过还有一个问题需要解决,就是编码的标号是从258开始,那么从多少结束呢?显然,我们不可能让其一直增大下去!于是,我们就规定一下:当标号达到4096时(GIF规范规定的是12位,超过12位的表达范围就得重来),我们就将整个标号集重新初始化,开始使用新的标记,提高了利用率和效率,何乐而不为呢!
LZW压缩算法就讲完了,算法的实现过程大致的说明白了,不过有些细节还是没有讲到,以后如果遇见了一定会在文中补充!
最后一项,LZW编码算法的核心代码:
/***这个结构体是为了用结构体的成员表示编码串和数字值***/ typedef struct Dictionary{ int value; /**编码的值**/ unsigned char prefix_string; /**与prefix作比较**/ unsigned char char_add; /**与suffix作比较**/ }Dictionary; Dictionary dict[MaxLength]; /**MaxLength = 4096**/
/***LZW压缩算法的实现过程***/ int * lzw_coding(unsigned char *src_ch, unsigned int *Prefix_Suffix, int src_length, unsigned int *Char_Stream) { /*** src_ch表示源数据串 *** Prefix_Suffix表示索引数组 *** src_length表示源数据串的长度 *** Char_Stream表示经过编码后的数字表 ***/ int i = 0; int j = 0; int k = 0; int temp = 0; int code = 258; // 从258开始 while(i < src_length) { Prefix_Suffix[j+1] = src_ch[i]; //源数据赋值给suffix if(Prefix_Suffix[j] == 0) { Prefix_Suffix[j] = Prefix_Suffix[j+1]; i++; continue; } temp = compare(Prefix_Suffix[j], Prefix_Suffix[j + 1]); if(dict[temp].value != UNUSED) //在串表中可以找到,UNUSED = -1 { Prefix_Suffix[j] = dict[temp].value; //如若相同变索引 } else //串表中找不到,如若不同则编码 { dict[temp].value = code++; dict[temp].prefix_string = Prefix_Suffix[j]; dict[temp].char_add = Prefix_Suffix[j+1]; Char_Stream[k++] = Prefix_Suffix[j]; Prefix_Suffix[j] = Prefix_Suffix[j + 1]; } i++; } return Char_Stream; }
/***比较索引数组的串是否在串表中出现过***/ int compare(unsigned char prefix, unsigned char suffix) { int i = 0; i = prefix % MaxLength; while(1) { if(dict[i].value == UNUSED) { return i; } if(dict[i].prefix_string = prefix && dict[i].char_add == suffix) { return i; } i++; } }
参考文献:http://tech.watchstor.com/management-115343.htm
第二节 Rice压缩算法
Rice编码:Rice encoding,是由Robert F. Rice发明的这个算法,我们直译过来就叫它“大米编码”。
Rice压缩算法的基本思想是:用较少的位来表示多个字(或数字),更重要的是,它能区分当前字(或数字)和下一个字(或数字)的位置。
有些人称RICE压缩算法是静态的Huffman编码算法,还是很有道理的。
附注:Rice压缩算法一般都是对较小的数字进行操作,因为数字越小,它需要的位就越少。
Rice压缩算法的原理:被除数 =除数 * 商 +余数。经过“大米”压缩算法压缩之后的结果就是由除数和余数组成的。
我先详细的解释一下这个原理:
令 S = Q * M + R(Q和R的组合将会是S的压缩结果),压缩结果的表示:
S:为将要压缩的数;
M:是一个常数,M = 2K;
Q:Q = S >> K;
R:R = S & (M - 1),R用K位表示。
其中对于K,K表示一个数的位数,而这个K是由一些数的平均系数来决定的。比如现有:10,12,14,18,36这五个数,你会发现前三个数的平均位数为4,于是这个K就是4了。也就是说,在某一范围内,一些数的出现次数较多,且这些数可用K位来表示,那么K就定下来了。
好了,原理说明白了,我们用一个例子实现编码的过程:
在“大米”压缩之前,我先说明一下常规方法,这样就可以和“大米”压缩算法作比较了,更加清晰的理解。
常规情况下,我们对上述五个数编码,即就是:1010 1100 1110 10010 100100。试想想,如果这样的话,解码的时候你怎样区分当前数和下一个数呢?这就比较麻烦了。所以,Rice算法正好解决了这个问题。
现对10,12,14,18,36这五个数用“大米”压缩算法进行编码,在原理中已经说明如何得到K,于是K = 4。那么就有:
首先,对于10,12,14这三个数可直接用4位表示,即1010,1100,1110。解码的时候已知K = 4,所以很容易。
接下来,对于18,可表示为:10010,根据编码原理:
M = 2K= 16;
Q = S >> K = 18 >> 4 = 10010 >> 4 = 0b0001;Q转为十进制是1,根据表示方法,1的后面跟1个0,即10;
R = S & (M - 1) = 18 & (16 - 1) = 10010 & 1111 = 0b0010
于是,压缩结果就是:100010。
最后,对于36,表示为:100100,根据编码原理:
M = 2K= 16;
Q = S >> K = 36 >> 4 = 100100 >> 4 = 0b0010;Q转为十进制的值是2,根据表示方法,1的后面跟2个0;
R = S & (M - 1) = 36 & (M - 1) = 100100 & 1111 = 0b0100
于是压缩结果就是:1000100。
怎么样,挺容易吧,这就是“大米”压缩算法。该算法的关键就是能辨别出当前数和下一个数的位置!解压缩的过程更简单了,我们已知K的值和Q与R的规范,直接解压就OK!
好了,最后就是压缩和解压缩的算法:
压缩: char * Rice_coding(char src) { if(src & 0xf0 == 0) //可直接用K位表示,标志位置0 { printf 直接输出K位; } else //超过K位,标志位置1 { Q = SRC >> K; temp_Q = (int)(Q & 0XFF); R = src & (M - 1); printf 1 + temp_Q + R; } }
解压缩: char Rice_decoding(char *src) { if(标志位为0) //源数据可用K位表示 { 直接取K位还原 } else //标志位为1 { 取Q的值,Q已知(从压缩过程中获得); R串 = src - Q串; S = Q × M + R; } }
第三节 结束语
想想、写写、画画......