深入解析数据压缩算法

正文

       所谓数据压缩,是指在不丢失信息的前提下,缩减数据量以减少存储空间,提高传输、存储和处理效率的一种技术方法。或者是按照一定的算法对数据进行重新组织,减少数据的冗余和存储的空间。

 

       能实现数据压缩的本质原因就是数据的冗余性。

       本系列将分为上下两个部分,介绍四种数据压缩算法,分别为Huffman压缩算法、RLE压缩算法、LZW压缩算法、Rice压缩算法

       其中本文将详解Huffman压缩算法和RLE压缩算法。

 

       详解数据压缩算法(下):http://blog.csdn.net/fengchaokobe/article/details/8020472

 

第一节 Huffman压缩算法


       huffman压缩算法可以说是无损压缩中最优秀的算法。它使用预先二进制描述来替换每个符号,长度由特殊符号出现的频率决定。其中出现次数比较多的符号需要很少的位来表示,而出现次数较少的符号则需要较多的位来表示。

 

       huffman压缩算法的原理:利用数据出现的次数构造Huffman二叉树,并且出现次数较多的数据在树的上层,出现次数较少的数据在树的下层。于是,我们就可以从根节点到每个数据的路径来进行编码并实现压缩。

 

       老办法,还是举个例子。

       假设有一个包含100000个字符的数据文件要压缩存储。各字符在该文件中的出现频度如下所示:

       在此,我会给出常规编码的方法和Huffman编码两种方法,这便于我们比较。

       常规编码方法:我们为每个字符赋予一个三位的编码,于是有:


       此时,100000个字符进行编码需要100000 * 3 = 300000位。


       Huffman编码:利用字符出现的频度构造二叉树,构造二叉树的过程也就是编码的过程。

这种情况下,对100000个字符编码需要:(45 * 1 + (16 + 13 + 12 + 9)*3 + (9 + 5)*4) * 1000 = 224000

 

孰好孰坏,例子说明了一切!好了,老规矩,下面我还是用上面的例子详细说明一下Huffman编码的过程。

       首先,我们需要统计出各个字符出现的次数,如下:

       接下来,我根据各个字符出现的次数对它们进行排序,如下:

       好了,一切准备工作就绪。

       在上文我提到,huffman编码的过程其实就是构造一颗二叉树的过程,那么我将各个字符看成树中将要构造的各个节点,将字符出现的频度看成权值。Ok,有了这个思想,here we go!

       构造huffman编码二叉树规则:

从小到大,

从底向上,

依次排开,

逐步构造。

       首先,根据构造规则,我将各个字符看成构造树的节点,即有节点a、b、c、d、e、f。那么,我先将节点f和节点e合并,如下图:

于是就有:

经过排序处理得:

 

        接下来,将节点b和节点c也合并,则有:

       于是有:

       经过排序处理得:

       第三步,将节点d和节点fe合并,得:

       于是有:

       继续,这次将节点fed和节点bc合并,得:

       于是有:

       最后,将节点a和节点bcfed合并,有:

       以上步骤就是huffman二叉树的构造过程,完整的树如下:

       二叉树成了,最后就剩下编码了,编码的规则为:01

       于是根据编码规则得到我们最终想要的结果:

       从上图中我们得到各个字符编码后的编码位:

       Ok,过程清楚了,我们来看看核心代码:由于Huffman编码在以前的文章已经给出过,故在这只给出链接!

       Huffman编码:http://blog.csdn.net/fengchaokobe/article/details/6969217

       好了,Huffman编码就讲完了。Go on!

 

 第二节 RLE压缩算法

 

       RLE:Run-length Encoding,译为“行程长度编码”,它是一个无损压缩的非常简单的算法。

 

       RLE压缩算法的原理:统计某一节字节在整个字节表中出现的次数,并以该字节和出现的次数作为编码的依据。

 

       好了,光说理论解决不了问题,还是用例子来说明。

现有如下一些字节数据:

       那么,首先我对上述每个数据出现次数做统计,即得到:

       ok,编码的依据得到了,万事俱备。由于RLE编码太简单了,于是我们一步就得到编码的结果了。如下:

       编码的过程简单,代码的实现也就很容易了,下面我们看看核心代码:

char * REL_Coding(char *src_ch, int length, char *dst_ch) /***	src_ch为原始字符串,  ***	length为原始字符串的长度,  ***	dst_ch为根据算法得到的编码  ***/ {         int     i = 0;         int     j = 0;         int     count = 0;         char    p = src_ch[0];          while(i <= length)	//开始编码         {                 if(p == src_ch[i])//如果有重复,计算其重复的次数                 {                         i++;;                         count++;                         continue;                 }                 dst_ch[j] = p;                 dst_ch[++j] = (count + '0');                 j++;                 count = 0;                 p = src_ch[i];	//下一次比较的开始位置         }          return dst_ch; }

       对于上述方法,有人提出:如果字节表中出现连续不重复的数据,就会因为设置太多的字节次数为而达不到压缩的效果。再想的坏一点,如果字符表中的各个字符只出现一次,也就是全部不重复,那么经过RLE编码之后,不仅没有实现压缩,反倒是增加了一倍。比如“ABCDEFG”,如果用上述方法,那么经过压缩后应为:A1B1C1D1E1F1G1,好坏一目了然。当然这是最坏的情况,但是我们必须考虑!那么此时我们该怎么办呢?

 

       针对上述问题,人们对算法做了一些改进。改进算法的核心思想是:我们知道一个字节是八位,那么用最高位来当做一个标志位,这个标志位如果为1,则表示后面跟的是重复的数据,如果为0则表示后面跟的是非重复的数据;我们用低七位来表示这个数据重复的次数。用下一个字节来表示这个字节数据。

 

       举个例子,我们现有一下数据:

对于表中的数据通过前后位的比较,有两种情况:

       1.如果有几个连续重复的数据,则最高位置1,并计算重复的次数;

        2.如果当前数据与下一个数据不同,则表示当前数据没有重复,置0,继续向后查找。

          注意:当出现连续出现不重复数据的情况时,如“ABCD”,此时只可用一个标志位来表示。

 

于是,便得到了压缩后的结果:

       我们来计算比较一下,压缩之前需要15个字节,而用改进算法压缩之后只需要11个字节。而且,改进的算法在原始数据越长的情况下,压缩的效果就越优秀。

       这么优秀的算法,必须给出核心代码,如下:

unsigned char * rle_coding(char * src_ch, int length, unsigned char * dst_ch) /***	src_ch为原始字符串,  ***	length为原始字符串的长度,  ***	dst_ch为根据算法得到的编码  ***/ {         int     i = 0;         int     j = 0;         int     k = 0;         int     count = 0;         char    p = src_ch[0];          while(i <= length)	//开始编码         {                 if(src_ch[i] == p)	//如果有重复,计算其重复的次数                 {                         count++;                         i++;                         continue;                 }                  if(count > 2)	//如果重复的次数满足条件,则最高位置1                 {                         dst_ch[j] = dst_ch[j] | 0x80;                 }                  dst_ch[j] = dst_ch[j] | count;	//低位保留重复的次数                 dst_ch[j + 1] = p;                 count = 0;                 p = src_ch[i];	//下一次比较的开始位置 				 				/*                 for(k = 0; k < 0x2; k++)                 {                         if(j % 2 == 0)                         {                                 printf("%x", dst_ch[j]);                                 j++;                         }                         else                         {                                 printf("%c", dst_ch[j]);                                 j++;                         }                 } 				*/ 				j++;         }         return dst_ch; }

       好了,RLE压缩算法就这些,怎么样,简单吧!本文的两种算法讲完了,其余的压缩算法会在下篇中跟进!
      

第三节 结束语

       想想、写写、画画......(未完,接下篇)