零零散散学算法之详解数据压缩算法(下)

深入解析数据压缩算法

 

前序

      

       开始本文之前,先回顾一下上篇。上篇讲解了几种数据压缩算法中的两种: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

       QQ = 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; 	} }


第三节 结束语

 

       想想、写写、画画......

 

你可能感兴趣的:(数据压缩)