LZSS算法

来源:互联网 发布:app源码是什么 编辑:程序博客网 时间:2024/04/27 17:39

  昨天看了下LZSS.C,就是那个4/6/1989 Haruhiko Okumura的经典代码。

  很久没有研究算法了,又没有详细的描述,只能从代码和注释里面去理解。还真花了我不少时间。
  首先讲解压,LZSS的编码是1 byte的flag,从低到高,如果bit=1,原样输出1 byte,如果bit=0,读取2 byte,输出长度和缓冲区位置保存在这2 byte中。其实标准的LZSS我还是第一碰到,以前碰到的多是输出长度和回溯距离的组合。LZSS则多了一个缓冲区,一般大小N = 4096(0x1000),也就是12 bits,缓冲区位置占掉了12 bits,那么输出长度就只能占用4 bits。考虑到bit=0时至少要占用2 bytes,所以输出长度为2时刚刚盈亏平衡,所以一般来说输出长度是从3开始的。在代码中THRESHOLD = 2,意思其实是长度必须大于2。这样的话输出长度的范围就是3-18。代码中F = 18,F就是最大的输出长度。我碰到到是一个改版,N = 0x800,也就是11 bits,输出长度变成了5 bits,THRESHOLD = 1,最后输出长度的范围是2-33。个人觉得THRESHOLD改成1实在是浪费了一个珍贵的输出长度编码。用了缓冲区和不用缓冲区的区别,我看就是多了一个字符串,就是缓冲区一开始填充的值。LZSS.C中默认填充的是空格,那么大概是专门为文本文件设计的。一般还是填充0比较多。具体怎么回事下面再描述。缓冲区的大小N,一开始先填充N-F区域,然后一边解压,一边循环的从N-F开始填充字符。一般来说开头的字符很难形成重复,所以LZSS压缩的特征往往 是FF xx xx xx ..(8 bytes) FF xx xx xx...(8 bytes),到后面出现大量重复了,就难以辨认了。如果一开头是一段空格的话,那么第一段就可以(pos = N-F-1, len = 8),这样就可以输出8个空格。如果不用缓冲区的话,开头的8个空格就要变成(空格), (N-F, 7),算是节约了1个byte。下面讲压缩,其实最简单的压缩就是做一个for循环,从头到尾一个个比较,最后保留匹配长度最大的那个。这样的话复杂度是O(n*N*F),其中n是待压 缩文本的大小,F是最大输出长度,这个值很小可以不管,N就是缓冲区大小或者回溯距离,只要n和N不是很大,速度都是秒的。LZSS.C中提供了一个优化的算法,其实整个代码也就这段有看头。首先定义了3个数组,lson, rson, dad组成了一颗二叉树,lson, dad的大小都是N+1,要注意rson[N]/dad[N]其实并没有被用到,N这个值在程序中被定义成NIL,故意多开一个只是为了方便。这种为了方 便的情况出现很多,就不多说了。然后是rson在N+1的基础上还多出了256,这是为了存放1 byte的所有编码,其实就是根。我觉得其实可以新开一个数组的,没有必要用rson这个名字。rson其实用来保存大于等于的字符串,lson保存小于 的字符串。dad就是保存dad。至于什么叫做大于等于,用过strcmp总有体会吧,或者看看下面的说明。

  算法我用例子来说明吧,比如一段文字:

  --- 我 是 标 尺 ---
  1234 567 89012 345678
  abcd abc abcde abcdef


  注:为了方便起见,rson['a']的含义其实是rson[N+1+'a'],文本位置从1开始,1其实是N-F

  1)   读取a,找到rson['a'],这个值初始为NIL,那么写入位置1。
  2)   读取b-d,rson['b']-rson['d']等于2-4
  3.a) 又读取a,此时rson['a'] = 1, 那么比较两个字符串(从1开始的和从5开始的),比较下来长度=3,比较的最后一步是位置8的'a'-位置4的'd',cmp<0,所以此时检查 lson[1],lson[1]当然还是NIL,所以不再比较了,把lson[1]设置成4。意思就是说1和4,都是'a'开头的字符串,4比1小。
  3.b) 下面是输出和补充字符,首先输出(1, 3),至于怎么写flag和那2byte我就不讲了。然后读入3 bytes,考虑如果本算法已经执行了一段时间了,填充区已经填满,那么读入的时候就占用了先前的字符,那么此时还要删除先前字符的节点。读的同时位置6 开始的'bc'也要添加入树中,不过就算匹配长度超过2,也不会输出罢了。
  4)   读取位置8的'a',rson['a] = 1, 比较两个字符串(位置8和位置1开始的字符串),最后结果是cmp = 'e'-'a' > 0,len = 4,检查rson[1] = NIL,那么写入rson[1] = 8,后面的步骤和3.b一样。
  5)   读取位置12的'e',rson['e'] = 12
  6)   读取位置13的'a',rson['a'] = 1,比较两个字符串(位置13和位置1开始的字符串),最后结果是cmp = 'e'-'a' > 0,len = 4,检查rson[1] = 8,那么再比较这两个字符串(位置13和位置8开始的字符串),最后结果是cmp = 'f' - 'a' > 0,len = 5,再检查rson[8] = NIL,那么rson[8] = 13。最后输出的就是(8, 5)。

    解释到这里应该就很清楚了,这里面有这样一个关系,就是如果当前字符串比这个节点大,那么只有往右支找才有前途,反之亦然。这个很好想,比如说根是 'abcde',左支是'abcaa',根和左支的相同字符数是3,如果当前字符串是'abz',和根的相同字符数是2,还小于3,那么到左支去也就是平 手,如果当前字符串是'abcdz',和根的相同字符数是4,那已经超过左支了。现在我是变化当前字符串,如果当前字符串不变,和根的相同字符数是N,根 的左支所有的子节点和根的相同字符数如果大于N,也最多和根平手,如果小于N,就肯定输了。去右支,虽然可能碰到'az'这种更差情况,但 'abcdzaaa'这种更好情况也只可能在右支出现。所以说去右支才有前途。
    最后还有个有意思的东西,就是在正式读取待压缩数据前,会将N-F前的F个字符加入到树中,作用我刚刚提到过,在开头有8个空格的情况下,可以节省 1byte,此时的输出时(pos = N-F-1, len = 8),那么只要添加一个字符不就行了么。其实考虑开头不是空格的情况,一段代码在中间部分出现了缩进(显然很多人喜欢将tab转成4个空格),于是依次出 现了4个空格,8个空格,12个空格等等。一般的回溯距离+输出长度的话,编码会是这样的(空格), (-1, 3),...,(-x1, 4), (-4, 4),...,(-x2, 8), (-4, 4),那么标准的LZSS,且加入过F个空格的话,编码就简单多了:(N-F-4, 4),...,(N-F-8, 8), ..., (N-F-12, 12)
  算法中还有些删除节点,边界判断之类的东西,这都是基础的东西,不讲了。这个优化算法的效率应该是O(n*logN*F)

2010年6月24日21:37:34 补充:

    关于效率有点问题,优化算法每一个字符都要添加进二叉树中,所以效率是O(n*logN*F),但是用循环遍历的不需要添加每一个字符,找到一个匹配串之 后,指针就向后移动了。所以效率应该是O(r*n*N*F),新增的系数r接近压缩率,但比压缩率要小,这个值不妨认为是0.1。那么只有在 logN < 0.1N的时候优化算法才划算。所以N应该大于2^6。

//lzss.h#include <stdio.h>class LZSS{enum LZSSDATA{inMEM=1,inFILE};unsigned char *buffer;int mpos,mlen;int *lson,*rson,*dad;unsigned char InType,OutType;                          //输入输出数据类型,指明是文件还是内存中的数据unsigned char *InData,*OutData;                        //输入输出数据指针FILE *fpIn,*fpOut;                                     //输入输出文件指针unsigned long InDataSize;                              //输入数据长度unsigned long InSize,OutSize;                          //已输入输出数据长度int GetByte();                                         //获取一个字节的数据void PutByte(unsigned char);                           //写入一个字节的数据void InitTree();//初始化串表void InsertNode(int);//插入一个表项void DeleteNode(int);//删除一个表项void Encode();                                         //压缩数据void Decode();                                         //解压数据public:LZSS();                                                //本类构造函数~LZSS();                                               //本类析构函数unsigned long Compress(unsigned char *,unsigned long,                        unsigned char *);               //内存中的数据压缩unsigned long Compress(unsigned char *,unsigned long,                        FILE *);                        //将内存中的数据压缩后写入文件unsigned long Compress(FILE *,unsigned long,FILE *);   //压缩文件unsigned long UnCompress(unsigned char *,unsigned long,                          unsigned char *);             //内存中的数据解压unsigned long UnCompress(FILE *,unsigned long,                          unsigned char *);             //将文件中的数据解压后写入内存unsigned long UnCompress(FILE *,unsigned long,FILE *); //解压文件};

//lzss.cpp#include "stdio.h"#include "lzss.h"#include "windows.h"#define N               4096#define F               18#define THRESHOLD       2#define NIL             NLZSS::LZSS(){buffer = new unsigned char[N+F-1];lson = new int[N+1];rson = new int[N+257];dad = new int[N+1];}LZSS::~LZSS(){delete buffer;delete lson;delete rson;delete dad;}//获取一个字节的数据int LZSS::GetByte(){if(InSize++>=InDataSize)return(EOF);switch(InType){case inMEM :return(*InData++);case inFILE:return(getc(fpIn));}return(EOF);}//写入一个字节的数据void LZSS::PutByte(unsigned char c){OutSize++;switch(OutType){case inMEM :*OutData++=c;return;case inFILE:putc(c,fpOut);return;}}//初始化串表void LZSS::InitTree(){int i;for(i=N+1;i<=N+256;i++)rson[i]=NIL;for(i=0;i<N;i++)dad[i]=NIL;}//插入一个表项void LZSS::InsertNode(int r){int i,p,cmp;unsigned char *key;cmp=1;key=&buffer[r];p=N+1+key[0];rson[r]=lson[r]=NIL;mlen=0;while(1){if(cmp>=0){if(rson[p]!=NIL)p=rson[p];else{rson[p]=r;dad[r]=p;return;}}else{if(lson[p]!=NIL)p=lson[p];else{lson[p]=r;dad[r]=p;return;}}for(i=1;i<F;i++)if((cmp=key[i]-buffer[p+i])!=0)break;if(i>mlen){mpos=p;if((mlen=i)>=F)break;}}dad[r]=dad[p];lson[r]=lson[p];rson[r]=rson[p];dad[lson[p]]=r;dad[rson[p]]=r;if(rson[dad[p]]==p)rson[dad[p]]=r;    else lson[dad[p]]=r;dad[p]=NIL;}void LZSS::DeleteNode(int p){int q;if(dad[p]==NIL)return;if(rson[p]==NIL)q=lson[p];else if(lson[p]==NIL)q=rson[p];else{q=lson[p];if(rson[q]!=NIL){do{q=rson[q];}while(rson[q]!=NIL);rson[dad[q]]=lson[q];dad[lson[q]]=dad[q];lson[q]=lson[p];dad[lson[p]]=q;}rson[q]=rson[p];dad[rson[p]]=q;}dad[q]=dad[p];if(rson[dad[p]]==p)rson[dad[p]]=q;    else lson[dad[p]]=q;dad[p]=NIL;}void LZSS::Encode(){int i,c,len,r,s,lml,cbp;unsigned char codebuf[17],mask;InitTree();codebuf[0]=0;cbp=mask=1;s=0;r=N-F;memset(buffer,' ',r);for( len=0; len<F && (c = GetByte()) != EOF; len++ )buffer[r+len]=c;if(len==0)return;for(i=1;i<=F;i++)InsertNode(r-i);InsertNode(r);do{if(mlen>len)mlen=len;if(mlen<=THRESHOLD){mlen=1;codebuf[0]|=mask;codebuf[cbp++]=buffer[r];}else{codebuf[cbp++]=(unsigned char)mpos;codebuf[cbp++]=(unsigned char)(((mpos>>4)&0xF0)|(mlen-(THRESHOLD+1)));}if((mask<<=1)==0){for(i=0;i<cbp;i++)PutByte(codebuf[i]);codebuf[0]=0;cbp=mask=1;}lml=mlen;for(i=0;i<lml&&(c=GetByte())!=EOF;i++){DeleteNode(s);buffer[s]=c;if(s<F-1)buffer[s+N]=c;s=(s+1)&(N-1);r=(r+1)&(N-1);InsertNode(r);}while(i++<lml){DeleteNode(s);s=(s+1)&(N-1);r=(r+1)&(N-1);if(--len)InsertNode(r);}}while(len>0);if(cbp>1)for(i=0;i<cbp;i++)PutByte(codebuf[i]);}void LZSS::Decode(){int i,j,k,r,c;unsigned int flags;for(i=0;i<N-F;i++)buffer[i]=' ';r=N-F;flags=0;for(;;){if(((flags>>=1)&256)==0){if((c=GetByte())==EOF)break;flags=c|0xFF00;}if(flags&1){if((c=GetByte())==EOF)break;PutByte(c);buffer[r++]=c;r&=(N-1);}else{if((i=GetByte())==EOF)break;if((j=GetByte())==EOF)break;i|=((j&0xF0)<<4);j=(j&0x0F)+THRESHOLD;for(k=0;k<=j;k++){c=buffer[(i+k)&(N-1)];PutByte(c);buffer[r++]=c;r&=(N-1);}}}}unsigned long LZSS::Compress(unsigned char *in,unsigned long insize,unsigned char *out){InType=inMEM;InData=in;InDataSize=insize;InSize=0;OutType=inMEM;OutData=out;OutSize=0;Encode();return(OutSize);}unsigned long LZSS::Compress(unsigned char *in,unsigned long insize,FILE *out){InType=inMEM;InData=in;InDataSize=insize;InSize=0;OutType=inFILE;fpOut=out;OutSize=0;Encode();return(OutSize);}unsigned long LZSS::Compress(FILE *in,unsigned long insize,FILE *out){InType=inFILE;fpIn=in;InDataSize=insize;InSize=0;OutType=inFILE;fpOut=out;OutSize=0;Encode();return(OutSize);}unsigned long LZSS::UnCompress(unsigned char *in,unsigned long insize,unsigned char *out){InType=inMEM;InData=in;InDataSize=insize;InSize=0;OutType=inMEM;OutData=out;OutSize=0;Decode();return(OutSize);}unsigned long LZSS::UnCompress(FILE *in,unsigned long insize,unsigned char *out){InType=inFILE;fpIn=in;InDataSize=insize;InSize=0;OutType=inMEM;OutData=out;OutSize=0;Decode();return(OutSize);}unsigned long LZSS::UnCompress(FILE *in,unsigned long insize,FILE *out){InType=inFILE;fpIn=in;InDataSize=insize;InSize=0;OutType=inFILE;fpOut=out;OutSize=0;Decode();return(OutSize);}



0 0
原创粉丝点击