再说LZ77压缩算法

LZ77 算法的基本流程。

一、从当前压缩位置开始,考察未编码的数据,并试图在滑动窗口中找出最长的匹配字符串,若是找到,则进行步骤 2,不然进行步骤 3。算法

二、输出三元符号组 ( off, len, c )。其中 off 为窗口中匹配字符串相对窗口边界的偏移,len 为可匹配的长度,c 为下一个字符。而后将窗口向后滑动 len + 1 个字符,继续步骤 1。数组

三、输出三元符号组 ( 0, 0, c )。其中 c 为下一个字符。而后将窗口向后滑动 len + 1 个字符,继续步骤 1。优化

咱们结合实例来讲明。假设窗口的大小为 10 个字符,咱们刚编码过的 10 个字符是:abcdbbccaa,即将编码的字符为:abaeaaabaee编码

咱们首先发现,能够和要编码字符匹配的最长串为 ab ( off = 0, len = 2 ), ab 的下一个字符为 a,咱们输出三元组:( 0, 2, a )设计

如今窗口向后滑动 3 个字符,窗口中的内容为:dbbccaaaba指针

下一个字符 e 在窗口中没有匹配,咱们输出三元组:( 0, 0, e )blog

窗口向后滑动 1 个字符,其中内容变为:bbccaaabae排序

咱们立刻发现,要编码的 aaabae 在窗口中存在( off = 4, len = 6 ),其后的字符为 e,咱们能够输出:( 4, 6, e )图片

这样,咱们将能够匹配的字符串都变成了指向窗口内的指针,并由此完成了对上述数据的压缩。ip

解压缩的过程十分简单,只要咱们向压缩时那样维护好滑动的窗口,随着三元组的不断输入,咱们在窗口中找到相应的匹配串,缀上后继字符 c 输出(若是 off 和 len 都为 0 则只输出后继字符 c )便可还原出原始数据。

固然,真正实现 LZ77 算法时还有许多复杂的问题须要解决,下面咱们就来对可能碰到的问题逐一加以探讨。

编码方法

咱们必须精心设计三元组中每一个份量的表示方法,才能达到较好的压缩效果。通常来说,编码的设计要根据待编码的数值的分布状况而定。对于三元组的第一个份量——窗口内的偏移,一般的经验是,偏移接近窗口尾部的状况要多于接近窗口头部的状况,这是由于字符串在与其接近的位置较容易找到匹配串,但对于普通的窗口大小(例如 4096 字节)来讲,偏移值基本仍是均匀分布的,咱们彻底能够用固定的位数来表示它。

编码 off 须要的位数 bitnum = upper_bound( log2( MAX_WND_SIZE ))

由此,若是窗口大小为 4096,用 12 位就能够对偏移编码。若是窗口大小为 2048,用 11 位就能够了。复杂一点的程序考虑到在压缩开始时,窗口大小并无达到 MAX_WND_SIZE,而是随着压缩的进行增加,所以能够根据窗口的当前大小动态计算所须要的位数,这样能够略微节省一点空间。

对于第二个份量——字符串长度,咱们必须考虑到,它在大多数时候不会太大,少数状况下才会发生大字符串的匹配。显然可使用一种变长的编码方式来表示该长度值。在前面咱们已经知道,要输出变长的编码,该编码必须知足前缀编码的条件。其实 Huffman 编码也能够在此处使用,但却不是最好的选择。适用于此处的好的编码方案不少,我在这里介绍其中两种应用很是普遍的编码。

第一种叫 Golomb 编码。假设对正整数 x 进行 Golomb 编码,选择参数 m,令

b = 2m

q = INT((x - 1)/b)

r = x - qb - 1

则 x 能够被编码为两部分,第一部分是由 q 个 1 加 1 个 0 组成,第二部分为 m 位二进制数,其值为 r。咱们将 m = 0, 1, 2, 3 时的 Golomb 编码表列出:

     值 x          m = 0         m = 1         m = 2         m = 3

-------------------------------------------------------------

      1               0           0 0          0 00          0 000

      2              10           0 1          0 01          0 001

      3             110          10 0          0 10          0 010

      4            1110          10 1          0 11          0 011

      5           11110         110 0         10 00          0 100

      6          111110         110 1         10 01          0 101

      7         1111110        1110 0         10 10          0 110

      8        11111110        1110 1         10 11          0 111

      9       111111110       11110 0        110 00         10 000

从表中咱们能够看出,Golomb 编码不但符合前缀编码的规律,并且能够用较少的位表示较小的 x 值,而用较长的位表示较大的 x 值。这样,若是 x 的取值倾向于比较小的数值时,Golomb 编码就能够有效地节省空间。固然,根据 x 的分布规律不一样,咱们能够选取不一样的 m 值以达到最好的压缩效果。

对咱们上面讨论的三元组 len 值,咱们能够采用 Golomb 方式编码。上面的讨论中 len 可能取 0,咱们只需用 len + 1 的 Golomb 编码便可。至于参数 m 的选择,通常经验是取 3 或 4 便可。

能够考虑的另外一种变长前缀编码叫作 γ 编码。它也分做先后两个部分,假设对 x 编码,令 q = int( log2x ),则编码的前一部分是 q 个 1 加一个 0,后一部分是 q 位长的二进制数,其值等于 x - 2q 。γ编码表以下:

     值 x      γ编码

---------------------

      1         0

      2        10 0

      3        10 1

      4       110 00

      5       110 01

      6       110 10

      7       110 11

      8      1110 000

      9      1110 001

其实,若是对 off 值考虑其倾向于窗口后部的规律,咱们也能够采用变长的编码方法。但这种方式对窗口较小的状况改善并不明显,有时压缩效果还不如固定长编码。

对三元组的最后一个份量——字符 c,由于其分布并没有规律可循,咱们只能老老实实地用 8 个二进制位对其编码。

根据上面的叙述,相信你必定也能写出高效的编码和解码程序了。

另外一种输出方式

LZ77 的原始算法采用三元组输出每个匹配串及其后续字符,即便没有匹配,咱们仍然须要输出一个 len = 0 的三元组来表示单个字符。试验代表,这种方式对于某些特殊状况(例如同一字符不断重复的情形)有着较好的适应能力。但对于通常数据,咱们还能够设计出另一种更为有效的输出方式:将匹配串和不能匹配的单个字符分别编码、分别输出,输出匹配串时不一样时输出后续字符。

咱们将每个输出分红匹配串和单个字符两种类型,并首先输出一个二进制位对其加以区分。例如,输出 0 表示下面是一个匹配串,输出 1 表示下面是一个单个字符。

以后,若是要输出的是单个字符,咱们直接输出该字符的字节值,这要用 8 个二进制位。也就是说,咱们输出一个单个的字符共须要 9 个二进制位。

若是要输出的是匹配串,咱们按照前面的方法依次输出 off 和 len。对 off,咱们能够输出定长编码,也能够输出变长前缀码,对 len 咱们输出变长前缀码。有时候咱们能够对匹配长度加以限制,例如,咱们能够限制最少匹配 3 个字符。由于,对于 2 个字符的匹配串,咱们使用匹配串的方式输出并不必定比咱们直接输出 2 个单个字符(须要 18 位)节省空间(是否节省取决于咱们采用何种编码输出 off 和 len)。

这种输出方式的优势是输出单个字符的时候比较节省空间。另外,由于不强求每次都外带一个后续字符,能够适应一些较长匹配的状况。

1. 原理部分:

  有两种形式的重复存在于计算机数据中,zip 就是对这两种重复进行了压缩。

  一种是短语形式的重复,即三个字节以上的重复,对于这种重复,zip用两个数字:1.重复位置距当前压缩位置的距离;2.重复的长度,来表示这个重复,假设这两个数字各占一个字节,因而数据便获得了压缩,这很容易理解。

  一个字节有 0 - 255 共 256 种可能的取值,三个字节有 256 * 256 * 256 共一千六百多万种可能的状况,更长的短语取值的可能状况以指数方式增加,出现重复的几率彷佛极低,实则否则,各类类型的数据都有出现重复的倾向,一篇论文中,为数很少的术语倾向于重复出现;一篇小说,人名和地名会重复出现;一张上下渐变的背景图片,水平方向上的像素会重复出现;程序的源文件中,语法关键字会重复出现(咱们写程序时,多少次先后copy、paste?),以几十 K 为单位的非压缩格式的数据中,倾向于大量出现短语式的重复。通过上面提到的方式进行压缩后,短语式重复的倾向被彻底破坏,因此在压缩的结果上进行第二次短语式压缩通常是没有效果的。

  第二种重复为单字节的重复,一个字节只有256种可能的取值,因此这种重复是必然的。其中,某些字节出现次数可能较多,另外一些则较少,在统计上有分布不均匀的倾向,这是容易理解的,好比一个 ASCII 文本文件中,某些符号可能不多用到,而字母和数字则使用较多,各字母的使用频率也是不同的,听说字母 e 的使用几率最高;许多图片呈现深色调或浅色调,深色(或浅色)的像素使用较多(这里顺便提一下:png 图片格式是一种无损压缩,其核心算法就是 zip 算法,它和 zip 格式的文件的主要区别在于:做为一种图片格式,它在文件头处存放了图片的大小、使用的颜色数等信息);上面提到的短语式压缩的结果也有这种倾向:重复倾向于出如今离当前压缩位置较近的地方,重复长度倾向于比较短(20字节之内)。这样,就有了压缩的可能:给 256 种字节取值从新编码,使出现较多的字节使用较短的编码,出现较少的字节使用较长的编码,这样一来,变短的字节相对于变长的字节更多,文件的总长度就会减小,而且,字节使用比例越不均匀,压缩比例就越大。

  在进一步讨论编码的要求以及办法前,先提一下:编码式压缩必须在短语式压缩以后进行,由于编码式压缩后,原先八位二进制值的字节就被破坏了,这样文件中短语式重复的倾向也会被破坏(除非先进行解码)。另外,短语式压缩后的结果:那些剩下的未被匹配的单、双字节和获得匹配的距离、长度值仍然具备取值分布不均匀性,所以,两种压缩方式的顺序不能变。

  在编码式压缩后,以连续的八位做为一个字节,原先未压缩文件中所具备的字节取值不均匀的倾向被完全破坏,成为随机性取值,根据统计学知识,随机性取值具备均匀性的倾向(好比抛硬币试验,抛一千次,正反面朝上的次数都接近于 500 次)。所以,编码式压缩后的结果没法再进行编码式压缩。

  短语式压缩和编码式压缩是目前计算机科学界研究出的仅有的两种无损压缩方法,它们都没法重复进行,因此,压缩文件没法再次压缩(实际上,能反复进行的压缩算法是不可想象的,由于最终会压缩到 0 字节)。

  短语式重复的倾向和字节取值分布不均匀的倾向是能够压缩的基础,两种压缩的顺序不能互换的缘由也说了,下面咱们来看编码式压缩的要求及方法:

首先,为了使用不定长的编码表示单个字符,编码必须符合“前缀编码”的要求,即较短的编码决不能是较长编码的前缀,反过来讲就是,任何一个字符的编码,都不是由另外一个字符的编码加上若干位 0 或 1 组成,不然解压缩程序将没法解码。

看一下前缀编码的一个最简单的例子:

符号 编码

A 0

B 10

C 110

D 1110

E 11110

有了上面的码表,你必定能够轻松地从下面这串二进制流中分辨出真正的信息内容了:

1110010101110110111100010 - DABBDCEAAB

要构造符合这一要求的二进制编码体系,二叉树是最理想的选择。考察下面这棵二叉树:

        根(root)

       0  |   1

       +-------+--------+

    0  | 1   0  |  1

    +-----+------+ +----+----+

    |     | |     |

    a      | d     e

     0  |  1

     +-----+-----+

     |     |

     b     c

要编码的字符老是出如今树叶上,假定从根向树叶行走的过程当中,左转为0,右转为1,则一个字符的编码就是从根走到该字符所在树叶的路径。正由于字符只能出如今树叶上,任何一个字符的路径都不会是另外一字符路径的前缀路径,符合要求的前缀编码也就构形成功了:

a - 00 b - 010 c - 011 d - 10 e - 11

接下来来看编码式压缩的过程:

为了简化问题,假定一个文件中只出现了 a,b,c,d ,e四种字符,它们的出现次数分别是

a : 6次

b : 15次

c : 2次

d : 9次

e : 1次

若是用定长的编码方式为这四种字符编码: a : 000 b : 001 c : 010 d : 011 e : 100

那么整个文件的长度是 3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99

用二叉树表示这四种编码(其中叶子节点上的数字是其使用次数,非叶子节点上的数字是其左右孩子使用次数之和):

          根

           |

      +---------33---------+

      |        |

   +----32---+     +----1---+

   |    |     |    |

+-21-+   +-11-+    +--1--+  

|   |   |   |    |   |

6  15  2  9    1   

(若是某个节点只有一个子节点,能够去掉这个子节点。)

         根

         |

        +------33------+

       |     |

    +-----32----+     1

    |      |

  +--21--+  +--11--+

  |   |  |   |

  6   15 2    9

如今的编码是: a : 000 b : 001 c : 010 d : 011 e : 1 仍然符合“前缀编码”的要求。

第一步:若是发现下层节点的数字大于上层节点的数字,就交换它们的位置,并从新计算非叶子节点的值。

先交换11和1,因为11个字节缩短了一位,1个字节增加了一位,总文件缩短了10位。

           根

            |

       +----------33---------+

       |        |

   +-----22----+     +----11----+

   |      |     |     |

+--21--+    1      2     9

|     |

6   15

再交换15和一、6和2,最终获得这样的树:

           根

            |

       +----------33---------+

       |        |

     +-----18----+    +----15----+

    |      |    |     |

  +--3--+    15   6     9

  |   |

  2   1

这时全部上层节点的数值都大于下层节点的数值,彷佛没法再进一步压缩了。可是咱们把每一层的最小的两个节点结合起来,常会发现仍有压缩余地。

第二步:把每一层的最小的两个节点结合起来,从新计算相关节点的值。

在上面的树中,第1、2、四三层都只有一或二个节点,没法从新组合,但第三层上有四个节点,咱们把最小的3和6结合起来,并从新计算相关节点的值,成为下面这棵树。

           根

            |

       +----------33---------+

       |         |

    +------9-----+    +----24----+

    |      |    |     |

   +--3--+    6   15    9

   |   |

  2  1

而后,再重复作第一步。

这时第二层的9小于第三层的15,因而能够互换,有9个字节增加了一位,15个字节缩短了一位,文件总长度又缩短了6位。而后从新计算相关节点的值。

           根

            |

       +----------33---------+

       |        |

       15     +----18----+ 

            |    |

         +------9-----+   9

         |      |

         +--3--+   6

         |   |

         2  1

这时发现全部的上层节点都大于下层节点,每一层上最小的两个节点被并在了一块儿,也不可能再产生比同层其余节点更小的父节点了。

这时整个文件的长度是 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63

这时能够看出编码式压缩的一个基本前提:各节点之间的值要相差比较悬殊,以使某两个节点的和小于同层或下层的另外一个节点,这样,交换节点才有利益。

因此归根结底,原始文件中的字节使用频率必须相差较大,不然将没有两个节点的频率之和小于同层或下层其余节点的频率,也就没法压缩。反之,相差得越悬殊,两个节点的频率之和比同层或下层节点的频率小得越多,交换节点以后的利益也越大。

在这个例子中,通过上面两步不断重复,获得了最优的二叉树,但不能保证在全部状况下,都能经过这两步的重复获得最优二叉树,下面来看另外一个例子:

                         根

                         |

              +---------19--------+

              |                   |

      +------12------+            7

      |              |

  +---5---+      +---7---+

  |       |      |       |

+-2-+   +-3-+  +-3-+   +-4-+

|   |   |   |  |   |   |   |

1   1   1   2  1   2   2   2

这个例子中,全部上层节点都大于等于下层节点,每一层最小的两个节点结合在了一块儿,但仍然能够进一步优化:

                         根

                         |

              +---------19--------+

              |                   |

      +------12------+            7

      |              |

  +---4---+      +---8---+

  |       |      |       |

+-2-+   +-2-+  +-4-+   +-4-+

|   |   |   |  |   |   |   |

1   1   1   1  2   2   2   2

经过最低一层的第4第5个节点对换,第3层的8大于第2层的7。

到这里,咱们得出这样一个结论:一棵最优二叉编码树(全部上层节点都没法和下层节点交换),必须符合这样两个条件:

1.全部上层节点都大于等于下层节点。

2.某节点,设其较大的子节点为m,较小的子节点为n,m下的任一层的全部节点都应大于等于n下的该层的全部节点。

当符合这两个条件时,任一层都没法产生更小的节点去和下层节点交换,也没法产生更大的节点去和上层节点交换。

上面的两个例子是比较简单的,实际的文件中,一个字节有256种可能的取值,因此二叉树的叶子节点多达256个,须要不断的调整树形,最终的树形可能很是复杂,有一种很是精巧的算法能够快速地建起一棵最优二叉树,这种算法由 D.Huffman(戴·霍夫曼)提出,下面咱们先来介绍霍夫曼算法的步骤,而后再来证实经过这么简单的步骤得出的树形确实是一棵最优二叉树。

霍夫曼算法的步骤是这样的:

·从各个节点中找出最小的两个节点,给它们建一个父节点,值为这两个节点之和。

·而后从节点序列中去除这两个节点,加入它们的父节点到序列中。

重复上面两个步骤,直到节点序列中只剩下惟一一个节点。这时一棵最优二叉树就已经建成了,它的根就是剩下的这个节点。

仍以上面的例子来看霍夫曼树的创建过程。

最初的节点序列是这样的:

a(6)  b(15)  c(2)  d(9)  e(1)

把最小的c和e结合起来

                   | (3)

a(6)   b(15)   d(9)   +------+------+

              |      |

              c     e

不断重复,最终获得的树是这样的:

       根

        |

   +-----33-----+

   |     |

   15   +----18----+   

       |       |

       9  +------9-----+

          |       |

         6     +--3--+

              |   |

              2  1

这时各个字符的编码长度和前面咱们说过的方法获得的编码长度是相同的,于是文件的总长度也是相同的: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63

考察霍夫曼树的创建过程当中的每一步的节点序列的变化:

6  15 2 9 1

6  15 9 3

15 9  9

15 18

33

下面咱们用逆推法来证实对于各类不一样的节点序列,用霍夫曼算法创建起来的树老是一棵最优二叉树:

对霍夫曼树的创建过程运用逆推法:

当这个过程当中的节点序列只有两个节点时(好比前例中的15和18),确定是一棵最优二叉树,一个编码为0,另外一个编码为1,没法再进一步优化。

而后往前步进,节点序列中不断地减小一个节点,增长两个节点,在步进过程当中将始终保持是一棵最优二叉树,这是由于:

1.按照霍夫曼树的创建过程,新增的两个节点是当前节点序列中最小的两个,其余的任何两个节点的父节点都大于(或等于)这两个节点的父节点,只要前一步是最优二叉树,其余的任何两个节点的父节点就必定都处在它们的父节点的上层或同层,因此这两个节点必定处在当前二叉树的最低一层。

2.这两个新增的节点是最小的,因此没法和其余上层节点对换。符合咱们前面说的最优二叉树的第一个条件。

3.只要前一步是最优二叉树,因为这两个新增的节点是最小的,即便同层有其余节点,也没法和同层其余节点从新结合,产生比它们的父节点更小的上层节点来和同层的其余节点对换。它们的父节点小于其余节点的父节点,它们又小于其余全部节点,只要前一步符合最优二叉树的第二个条件,到这一步仍将符合。

这样一步步逆推下去,在这个过程当中霍夫曼树每一步都始终保持着是一棵最优二叉树。

因为每一步都从节点序列中删除两个节点,新增一个节点,霍夫曼树的创建过程共需 (原始节点数 - 1) 步,因此霍夫曼算法不失为一种精巧的编码式压缩算法。

附:对于 huffman 树,《计算机程序设计艺术》中有彻底不一样的证实,大意是这样的:

1.二叉编码树的内部节点(非叶子节点)数等于外部节点(叶子节点)数减1。

2.二叉编码树的外部节点的加权路径长度(值乘以路径长度)之和,等于全部内部节点值之和。(这两条均可以经过对节点数运用数学概括法来证实,留给你们作练习。)

3.对 huffman 树的创建过程运用逆推,当只有一个内部节点时,确定是一棵最优二叉树。

4.往前步进,新增两个最小的外部节点,它们结合在一块儿产生一个新的内部节点,当且仅当原先的内部节点集合是极小化的,加入这个新的内部节点后还是极小化的。(由于最小的两个节点结合在一块儿,并处于最低层,相对于它们分别和其余同层或上层节点结合在一块儿,至少不会增长加权路径长度。)

5.随着内部节点数逐个增长,内部节点集合总维持极小化。

2.实现部分

  若是世界上从没有一个压缩程序,咱们看了前面的压缩原理,将有信心必定能做出一个能够压缩大多数格式、内容的数据的程序,当咱们着手要作这样一个程序的时候,会发现有不少的难题须要咱们去一个个解决,下面将逐个描述这些难题,并详细分析 zip 算法是如何解决这些难题的,其中不少问题带有广泛意义,好比查找匹配,好比数组排序等等,这些都是说不尽的话题,让咱们深刻其中,作一番思考。

咱们前面说过,对于短语式重复,咱们用“重复距当前位置的距离”和“重复的长度”这两个数字来表示这一段重复,以实现压缩,如今问题来了,一个字节能表示的数字大小为 0 -255,然而重复出现的位置和重复的长度均可能超过 255,事实上,二进制数的位数肯定下来后,所能表示的数字大小的范围是有限的,n位的二进制数能表示的最大值是2的n次方减1,若是位数取得太大,对于大量的短匹配,可能不但起不到压缩做用,反而增大了最终的结果。针对这种状况,有两种不一样的算法来解决这个问题,它们是两种不一样的思路。一种称为 lz77 算法,这是一种很天然的思路:限制这两个数字的大小,以取得折衷的压缩效果。例如距离取 15 位,长度取 8 位,这样,距离的最大取值为 32 k - 1,长度的最大取值为 255,这两个数字占 23 位,比三个字节少一位,是符合压缩的要求的。让咱们在头脑中想象一下 lz77 算法压缩进行时的状况,会出现有意思的模型:

   最远匹配位置->          当前处理位置->

───┸─────────────────╂─────────────>压缩进行方向

   已压缩部分             ┃    未压缩部分

  在最远匹配位置和当前处理位置之间是能够用来查找匹配的“字典”区域,随着压缩的进行,“字典”区域从待压缩文件的头部不断地向后滑动,直到达到文件的尾部,短语式压缩也就结束了。

  解压缩也很是简单:

         ┎────────拷贝────────┒

 匹配位置    ┃          当前处理位置  ┃

   ┃<──匹配长度──>┃       ┠─────∨────┨

───┸──────────┸───────╂──────────┸─>解压进行方向

   已解压部分              ┃    未解压部分

  不断地从压缩文件中读出匹配位置值和匹配长度值,把已解压部分的匹配内容拷贝到解压文件尾部,遇到压缩文件中那些压缩时未能获得匹配,而是直接保存的单、双字节,解压时只要依次直接拷贝到文件尾部便可,直到整个压缩文件处理完毕。

  lz77算法模型也被称为“滑动字典”模型或“滑动窗口”模型,因为它限制匹配的最大长度,对于某些存在大量的极长匹配的文件来讲,这种折衷算法显出了缺陷。另有一种lzw算法对待压缩文件中存在大量极长匹配的状况进行了彻底不一样的算法设计,而且只用一个数字来表示一段短语,下面来描述一下lzw的压缩解压过程,而后来综合比较二者的适用状况。

  lzw的压缩过程:

1) 初始化一个指定大小的字典,把 256 种字节取值加入字典。

2) 在待压缩文件的当前处理位置寻找在字典中出现的最长匹配,输出该匹配在字典中的序号。

3) 若是字典没有达到最大容量,把该匹配加上它在待压缩文件中的下一个字节加入字典。

4) 把当前处理位置移到该匹配后。

5) 重复 二、三、4 直到文件输出完毕。

  lzw 的解压过程:

1) 初始化一个指定大小的字典,把 256 种字节取值加入字典。

2) 从压缩文件中顺序读出一个字典序号,根据该序号,把字典中相应的数据拷贝到解压文件尾部。

3) 若是字典没有达到最大容量,把前一个匹配内容加上当前匹配的第一个字节加入字典。

4) 重复 二、3 两步直到压缩文件处理完毕。

  从 lzw 的压缩过程,咱们能够概括出它不一样于 lz77 算法的一些主要特色:

1) 对于一段短语,它只输出一个数字,即字典中的序号。(这个数字的位数决定了字典的最大容量,当它的位数取得太大时,好比 24 位以上,对于短匹配占多数的状况,压缩率可能很低。取得过小时,好比 8 位,字典的容量受到限制。因此一样须要取舍。)

2) 对于一个短语,好比 abcd ,当它在待压缩文件中第一次出现时,ab 被加入字典,第二次出现时,abc 被加入字典,第三次出现时,abcd 才会被加入字典,对于一些长匹配,它必须高频率地出现,而且字典有较大的容量,才会被最终完整地加入字典。相应地,lz77 只要匹配在“字典区域”中存在,立刻就能够直接使用。

3) 一个长匹配被加入字典的过程,是从两个字节开始,逐次增加一个字节,肯定了字典的最大容量,也就间接肯定了匹配的可能的最大长度。相对于 lz77 用两个数字来表示一个短语,lzw 只用一个数字来表示一个短语,所以,“字典序号”的位数能够取得多一点(二进制数多一位,意味着数值大一倍),也就是说最长匹配能够比 lz77 更长,当某些超长匹配高频率地出现,直到被完整地加入字典后,lzw将开始弥补初期的低效,逐渐显出本身的优点。

  能够看出,在多数状况下,lz77 拥有更高的压缩率,而在待压缩文件中占绝大多数的是些超长匹配,而且相同的超长匹配高频率地反复出现时,lzw 更具优点,GIF 就是采用了 lzw 算法来压缩背景单1、图形简单的图片。zip 是用来压缩通用文件的,这就是它采用对大多数文件有更高压缩率的 lz77 算法的缘由。

  接下来 zip 算法将要解决在“字典区域”中如何高速查找最长匹配的问题。

转载自:http://csy8217.blog.163.com/blog/static/9446787200811111943266/

相关文章
相关标签/搜索