改进LZW压缩算法

声明:以下方法由本人与whycadi提出,由本人整理发表,未经作者允许禁止使用或实现。

LZW压缩算法是由LZ78压缩算法改进而来,具有压缩速度快和动态还原码表的特点。然而,LZW压缩算法有着压缩率低,码表易快速膨胀,重复字符利用率低的问题。我对于改进LZW压缩算法有几个想法:

1,动态分配码字(针对字典管理)

仔细研究过LZW压缩算法的朋友们都知道,LZW压缩算法输出的码字就是码表索引值,而索引值必然需要8位以上来储存(码表中有压缩价值的元素的索引值都大于256),使得资源浪费极其严重。我们可以在解压和压缩时都将字典作为一个列表使用,一旦某个元素被输出,就将该元素插入到列表首项。在解压时,只需按相同方法对列表进行管理,即可正确还原数据。

示例:

143,123,1,0,453,2,2……

143被使用后索引值变为了0;

123被使用后索引值变为0,143的索引值变成1;

1再次被使用后索引值变为0,123索引值变为1;

0第三次被使用后索引值变为0,其他元素的索引值不变;

453被使用后索引值变为0,0变为1,1变为2;

2再次被使用后索引值变为0,0变为1,1变为2;

2第四次被使用,2变为0,0变为1,1变为2;

……

2,特殊标识码(针对输出优化)

我们只需仔细观察原版LZW压缩算法输出的列表,就会发现输出的元素都是整数类型,且一个码字可能会被输出多次。我们其实也可以利用到这个特点,对算法进行改进。在LZ系列压缩算法里,我们可以尝试参照一下LZ77算法的输出。

1,在输出码字前,判断该码字是否已经输出过一次:

(是):输出一个字符串类型的特殊标识码,这个特殊标识码是输出的码字在输出列表里的索引值

(否):直接输出一个整数类型的码字。

示例一下,以免你们看不懂:

……19647(第678次输出),4677,"678",16745,"681"……

这里使用特殊标识码“"678"”代替了已经输出过的19647。如果直接输出码字,则需要用5个字节;使用特殊标识码则仅需4个字节。这个改进对于大文件压缩是特别有效果的。

2,在读取码字时,判断该码字是否为字符串类型:

(是):把码字进行整数,然后输出输入表中对应的那一项码字,再处理这个输出的码字。

(否):直接处理这个码字

示例:

……19647(第678次输出),4677,"678",16745,"681"……

处理第一遍后:……19647,4677,19647,16745,16745……

然后就可以直接处理了(我没在这里处理是因为这些并不完整而且也是随机写出来的示例,所以不能处理出有效字符串)。处理的过程是和原版的是相同的,也是还原p和c的值以及码表。

3,LZ77编码与LZW编码混合编码(针对编码方式)

下文摘自:改进LZW压缩算法的一些想法(LZ77,双字典)_lzw算法优化-CSDN博客

      根据以上的几种方法,我思考了几天,想出了一些方法,看能不能把以上几种方法的优点结合起来,而仅增加很少的工作量。我的想法是结合LZ77(第一种方法),再使用双字典方式,即原字典满了或充满到一定的程度,在继续使用原字典的同时,新建一个新的字典,等新字典达到一定程度后,再切换到新字典上。

      首先分析标准LZW算法,LZW算法的标记是以一个{前缀、后缀}对的形式组成的,并且每次只增加一个标记,比如在文件开头处中有一个单词“abced”,那么第一次会生成以下的标记

flag1 :{a, b}

flag2 :{b ,c}

……

      如果在后面又遇到一次“abcde”,算法只会匹配到 flag1: a b ,同时增加一个标记 flagX: {flag1,c},即ab c。如此必须要出现多次"abced"才能把整个单词放入一个标记中。

      那么如果在第二次出现“abcde”后,在匹配了flag1 {a,b}后,没有匹配的标记了,但是并不直接输出flag1,而是在flag1出现的位置继续向后搜索一下,看是不是有更多的匹配,那么就可以发现其实还有可以匹配的,那么此时就输出一个{flag1,匹配长度}的标记,同时把{ab,c},{abc,d},{abcd,e}都加入为新的标记,那么下次再遇到“abced”的时候就能直接输出标记了。

     为了实现以上的算法,需要对LZW的方法做一些修改。

     首先是要把LZW标记和LZ77标记区分开,最简单的方法是加一些标志位,既然要增加标志位,不如把元字符也区分开来,考虑到实际输出中LZW标记占的比例最大,所以可以规定:

“0” 位开头为LZW标记,内容为{前缀,后缀,位置},标记从0开始,当然要规定一些协议标记,比如 0为Clear , 1为end,还可以设定其它的一些标记以方便解压,比如 2为增加标记位宽,3为开始建立新字典,4为切换字典等,建议真正的标记从16开始。标记记录的长度从1+4开始,慢慢增加到1+最大位宽(建议不小于12位,不超过16位)。LZW标记的内容也要增加一项,即标记第一次产生的位置 ;
“10”开头为元字符,即一个元字符占10位;
“11”开头为LZ77标记,LZ77 标记内容为{LZW标记,后续匹配长度};
 

    我们以“abcdeabcde"这个字符串,用两种方法比较一下输出,以 “Z”表示LZW标记,“L”表示L77标记

LZW方法: 标记 z1=ab ,z2=bc,z3=cd,z4=de,z5=da,z6=abc,z7=cde

                输出 "abcde",z1,z3,"e"

改进方法: 标记 z1={ab,1},z2={bc,2},z3={cd,3},z4={de,4},z5={abc,7},z6={abcd,8},z7={abcde,9}

                输出"abcde",L{z1,3}

    可见改进方法输出少了一些,特别是如果再次出现"abcde",LZW方法还要输出两个标记 z6,z4,而改进方法则可以直接输出标记z7。

    使用改进方法有个问题,就是如果出现了大段的重复,比如很多文件里有大量的0,如果不限制的话那么可能一次就要产生很多的标记,一下子把字典给填满了,所以我觉得可能应该限制一下一次产生标记的数量,考虑到大量的文件不太可能有频繁的过长的匹配,所以限制在8~16个可能比较好。如一个文件开头里有连续256个0,两种方法比较

输入"000000……0” 256个

LZW: 标记 z1=00,z2=000,z3=0000……

           输出 “00”, z1,z2,z3,……

改进方法: 标记 z1={00,1},z2={000,4},z3={0000,5}……限制到8个或16个

          输出  “00”,L{z1,252}  (和z1相同,并且后面还有252个字符相同,都是0)

注意上面,在使用L77标记时,比较时可以超过当前输入开始的位置,这对表示大段重复的字符极有好处。

如果此时不限制一次增加的标记数量,就会增加大量的垃圾标记,所以限制增加个数是很有必要的。至于限制在多少比较合适,需要实际测试一下才知道。

       另外,如果后面剩余匹配长度只有1个字符的话,似乎也没有必要使用L77方式,因为L77标记比LZW标记要长一位标志位和长度信息的位,只增加1个字符的话有点划不来。

还有一个问题就是后面的剩余匹配长度用多少位来表示?我想大部分情况下都是短匹配,不会有太多的长匹配,所以我想可以分两档,一档用4位,1位表示位宽,3位表示2~9这8种长度,另一档用8或9位,1位表示位宽,其它位表示 10~137(7位)或10~26

————————————

以上片段为摘录

总结

这些方法之间各有各的特点,而且针对的方向不同。对于实际应用中,应该注意这三种方法的兼容性,适当选择合适的方法以满足自己的需求,避免多种方法同时使用产生的错误导致压缩失败,在应用前应谨慎测试是否能正确压缩和解压。我想了一下,第一种和第二种方法结合没有兼容性问题,但是对于一些大量重复的短文本压缩率不高。这几种方法对于LZW压缩算法有较高的性能提升,不得使用或侵占版权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值