所谓数据压缩,是指在不丢失信息的前提下,缩减数据量以减少存储空间,提高传输、存储和处理效率的一种技术方法。或者是按照一定的算法对数据进行重新组织,减少数据的冗余和存储的空间。
能实现数据压缩的本质原因就是数据的冗余性。
本系列将分为上下两个部分,介绍四种数据压缩算法,分别为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二叉树的构造过程,完整的树如下:
二叉树成了,最后就剩下编码了,编码的规则为:左0右1
于是根据编码规则得到我们最终想要的结果:
从上图中我们得到各个字符编码后的编码位:
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压缩算法就这些,怎么样,简单吧!本文的两种算法讲完了,其余的压缩算法会在下篇中跟进!
第三节 结束语
想想、写写、画画......(未完,接下篇)