改进LZW压缩算法的一些想法(LZ77,双字典)

  前几天用Python实现了一个LZW算法后,很自然的又想怎么样可以在不显著增加开销的前提下改进压缩率。在网上找了一些文章后,主要发现了以下的几种途径:

  1.  混合使用LZ77和LZ78。即在发现标记匹配结束后,不立即结束匹配,而是再在一个滑动窗口中寻找是否有更好的匹配,如果有就以{位置,匹配长度}组的形式写入输出流中,并把该字符串逐个增加为标记。
  2. 在字典标记写满后,不立即清除字典,而是继续使用该字典,直到发现压缩率下降都到一定长度后再清除字典
  3. 不清除全部字典,而是根据使用率清除部分字典

      第一种途径,应该是能够很大地提高压缩比的。不过问题在于这个滑动窗口怎么选。而且一旦匹配了一个长字符串,会一下子增加很多的标记,使字典增加过快。

      等二种方法应该有不错的效果,不过如果万一源文件的统计规律发生了较大的变化,等发现字典不合适后再重建可能就来不及了。如果和其它的方法结合使用可能更好。

      等三种算法太复杂,要统计使用率,还要排序,更麻烦地是如何删除部分标记而不影响其它的标记。

 

      另外我自己还想了一下,LZW里面元字符从0~255,标记从256开始,如果使用位紧缩方式的话,后面不论元字符还是标记,所占用的位都越来越长,如果增加一位标记,使元字符和标记能够区分开,标记从0开始,那么元字符占用的长度就稳定在8+1位,而标记位长则按标记数量的增加逐渐变长,是不是可以有较好的效果。不过实践了一下,发现效果不是很好,大部分情况下压缩率略有下降,因为这种方式增加了1位标记的长度,而压缩后标记占的比例越大于元字符的比例,所以从元字符上节省的效果不明显。

 

      根据以上的几种方法,我思考了几天,想出了一些方法,看能不能把以上几种方法的优点结合起来,而仅增加很少的工作量。我的想法是结合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标记占的比例最大,所以可以规定:

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

 

       以上是结合了LZ77算法的方法,该方法仅仅在LZW方法上增加了一次后向匹配,在标记中增加了一个位置记录,因此增加的工作量很小。至于实际的效果如何,还需要试验一下才知道。

接下来说说双字典方法。

       LZW方法是字典满了以后,就把字典清空,再重新建立新字典,这样在切换字典的时候压缩效率就会立即下降,直到字典建立到一定的程度压缩率才会提升。上面提到的第二、三种方法都是针对这个问题来的,但是操作上都有些麻烦,特别是删除部分字典内容的方法,很难编程,运算开销也大。而且结合了LZ77算法后,由于需要和前面的内容后向匹配,需要保留前面标记产生处的内容,如果长期不清理字典标记的话,就需要保留很多的前面数据。所以字典还是应该及时清除。

如果在旧字典快满未满的时候,就根据当前的输入建立新的字典,那么当旧字典满了后,新字典也有一定的内容了,那么就可以切换到新字典上来而不会出现压缩率的急剧下降,同时也可以从内存里移除新字典开始建立以前的输入。并且此时标记的位宽也可以减少到新字典内标记的当前位宽。

       建立新字典可以采用与旧字典一样的方法,当然为简化也可以省略后向匹配工作,而就采用LZW方法,直到新字典切换成为工作字典后再采用新方法。

       另外开始建立新字典的时机也需要仔细考虑,一种方式是在旧字典将满未满的时候,到底多少比较合适,需要经过实验才知道。另一种策略是在旧字典满了后开始建立新字典,而继续使用旧字典一段时间,等到新字典达到一定的程度后再切换。可以通过实验测试一下压缩率和字典满度之间的关系,当然也可以采用自适应方式,在压缩过程中自动统计,选择一个最佳的满度。

       使用双字典的方法,增加了一定的内存和算法开销,不过LZW算法本身就是个开销比较小的算法,所以增加的开销也不大,并且编程简单,所以我想应该还是比较快的。

 

       以上就是我对改进LZW算法的一些想法,具体效果怎么样,只有实验了才知道,可惜工作太忙,最近是没有时间搞这个东西了。只能先把想法写下来,以后再试了。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值