LZ77与LZ78

LZ77与LZ78是以色列计算机科学家亚伯拉罕·蓝波(Abraham Lempel) 与杰可布·立夫 (Jacob Ziv) 在1977年以及1978年发表之论文中的两个无损数据压缩算法。这两个算法是大多数LZ算法变体如LZW、LZSS以及其它一些压缩算法的基础。与最小冗余编码器或者行程长度编码器不同，这两个都是基于字典的编码器。LZ77最初是带有“滑动窗”（）的压缩算法，这ω个算法后来证明等同于LZ78中首次出现的显式字典编码技术。
LZ77.
定义.
LZ77 对字符串 "x" [1 .. n]的分解是将其拆分并压缩为非空子串 "w"1,"w"2,…,"wk"​​，满足以下条件：
这些子串 "wj" 被称为因子或短语。
根据定义我们可以立即知道"w"1​="x" [1]。其中"x" [1 .. n]表示x中从第i位到第j位的字符串。"x"[i]是字符串"x" [i .. i]的简写。
算法.
计算 LZ77 压缩的算法核心思想是将已处理的字符串前缀用作字典。为了限制搜索时间，实际应用中通常限制字典的大小，因此通常使用滑动窗口（sliding window）。滑动窗口既限制字典大小，也限制要处理的文本范围（预览缓冲区）。压缩过程中，字符逐步从预览缓冲区移动到字典中。实际应用中，字典缓冲区通常包含数千个字符，而预览缓冲区则包含约100个字符或更少。
算法输出由三元组组成，可以用来恢复原始文本。对于因子 "wj" 的三元组形式为 ("pos","len","λ")：
使用滑动窗口时，"pos" 是字典的边界的相对位置，新字符以 (0,0,"λ") 表示。此输出格式无需显式存储字典即可重建文本。
Pseudocode LZ77-Compressionsalgorithmus:
 while input buffer is not empty do 
 match := longest repeated occurrence of input that begins in window
 if match exists then
 pos := distance to start of match
 len := length of match
 λ := char following match in input
 else
 pos := 0
 len := 0
 λ := first char of input
 end if
 output (pos, len, λ)
 discard "len" + 1 chars from front of window
 s := pop "len" + 1 chars from front of input
 append s to back of window
 repeat
压缩算法举例.
我们以字符串 codice_1 为例说明 LZ77 压缩的计算过程。算法采用字典长度为12（紫色区域）和预览缓冲区（黄色区域）长度为10的滑动窗口模型。算法输出的三元组包括：
其中参数pos是距离字典右边界的相对距离。
第一个看到的字符未知，因此将第一个 codice_2 编码为 codice_3。在第二行，字典中已有 codice_2（标记为绿色），因此 codice_5 被作为新字符记录。在第三行，LZ77 算法出现特殊情况：匹配的字符串延伸到预览缓冲区。第4行和第5行与前两行类似，但在最后一个三元组中添加了结束标记，因为文本已完全压缩且无后续字符。
关键点：
这种方法通过减少冗余实现更高的压缩效率。
运行时间分析.
该算法的运行时间为 Θ("n")，因为在压缩过程中，文本只被遍历一次。这在字典和预览缓冲区大小恒定的情况下成立，同时对大型文本进行匹配搜索的开销可以忽略不计。然而，在实际应用中，如果没有额外的数据结构支持，使用普通搜索方式的实现通常较慢。
LZ77解压算法.
解压 LZ77 压缩文件比压缩更简单，因为无需进行匹配字符串的搜索。其运行时间为线性 Θ("n")，即与原始文本长度相同的步骤数。例如我们解压 x = (0, 0, a) (1, 1, c) (3, 4, b) (3, 3, a) (12, 3, end):
解压后的完整文本为：codice_1。
The pseudocode LZ77 decompression algorithm with a sliding window:
 foreach triplet (offset, length, symbol) do
 if length &gt; 0 then
 traverse the previous output backward and output characters until the specified length is reached,
 restarting at offset in case of overflow;
 end if
 output the symbol
 repeat
优点与缺点.
LZ77 的一个主要优点是，它可以在完全不了解文本内容的情况下进行压缩，同时没有专利限制。其继任者 LZ78 需要初始字典来重建文本（通常是包含所有单个符号的简单字典），并且直到2004年部分受专利保护。
但是LZ77 对于小型或非自然语言文本的压缩效果有限，甚至可能增加数据量。例如，原文本有16个字符，而压缩结果为15个字符，仅节省了1个字符。尽管如此，LZ77 作为预处理器，与其他压缩方法（如哈夫曼编码）结合时，效果显著。
解压缩
优化算法：无滑动窗.
为了更高效地计算 LZ77 压缩字符串，以下的做法是在完整的字符串上计算分解因子，无需使用滑动窗。
对于字符串的 LZ77 因式分解，可以借助额外的数据结构实现高效的运行时间。对于一个字符串 x，记 SA 为后缀数组（Suffixarray），ISA 为 x 的反向逆后缀数组（inverse Suffixarray），即 ISA[i]=j 当且仅当 SA[j]=i。此外，记 lcp(i,j) 为字符串 x[SA[i]..n] 和 x[SA[j]..n] 的最长公共前缀的长度，其中 n=∣x∣。
计算利用了以下事实：对于字符串 x 中的因子 wj 且满足 ∣wj​∣&gt;1，为了确定元组位置 pos，只需考虑文本位置 SA[NSV[ISA[j]]] 和 SA[PSV[ISA[j]]]。其中：
算法伪代码.
算法 codice_8 返回需要压缩的下一个字符串的起始位置：PSV和NSV都是计算SA数组中对应的最大、最小值位置。
Algorithmus LZ_Factor(k, psv, nsv):
 i=ISA[k]
 if lcp(k, SA[psv[i]) &gt; lcp(k, SA[nsv[i]]); then
 (pos, len) &lt;- (SA[psv[i]], lcp(k, SA[psv[i]))
 else
 (pos, len) &lt;- (SA[nsv[i]], lcp(k, SA[nsv[i]])
 end if
 if len = 0; then
 pos &lt;- x[k]
 end if
 if k + len &gt; n; then
 print Factor:(pos, len, 'Ende')
 else
 print Factor:(pos, len, x[k+len])
 end if
 return k + max(len, 1)
我们首先直观地解释如何计算从位置 k 开始的下一个 LZ 因子:
考虑字符串 x=aaaba 且 k=1。在字符串 x 的后缀数组 SA 中，我们从索引 i=ISA[k] 开始，此处i=ISA[1]=2，我们可以查表得到psv[2]=0，nsv[2]=6。而无论是lcp[i,psv]还是lcp[i,nsv]都是0，因此输出第一个压缩后的因子Factor(a,0)。下一个需要查找的位置为 (k+max(len,1))=2.
类似地，当k=2时。在字符串 x 的后缀数组 SA 中，我们查询索引 i=ISA[2]=3，我们可以查表得到SA[psv[3]]=SA[2]=1，nsv[3]=6。此时lcp[2,1]=2，lcp[2,6]=0，因此输出第二个压缩后的因子Factor(1,2)。下一个需要查找的位置为 (k+max(len,1))=4.
当k=4时。在字符串 x 的后缀数组 SA 中，我们查询索引 i=ISA[4]=5，我们可以查表得到SA[psv[5]]=SA[4]=3，nsv[5]=6。此时lcp[4,3]=0，lcp[4,6]=0，因此输出第三个压缩后的因子Factor(b,0)。下一个需要查找的位置为 (k+max(len,1))=5.
当k=5时。在字符串 x 的后缀数组 SA 中，我们查询索引 i=ISA[5]=1，我们可以查表得到psv[1]=0，SA[nsv[1]]=SA[2]=1。此时lcp[5,0]=0，lcp[5,1]=1。因为k+len=6&gt;5，因此输出第四个压缩后的因子Factor(1,1,end)。
最后我们得到字符串x=aaaba的压缩因子为(a,0)(1,2)(b,0)(1,1,end)。
formula_1 和 formula_2 可以由 formula_3 数组计算得到:
 for i &lt;- 2 to n+1; do
 j &lt;- i-1
 while defined(j) and SA[i] &lt; SA[j]; do
 NSV[j] &lt;- i
 j &lt;- PSV[j]
 end while
 PSV[i] &lt;- j
 end for
最后终 LZ77压缩算法对任意字符串formula_4：
 calculate SA, ISA, NSV and PSV for x
 k &lt;- 1
 while k &lt;= n; do
 k &lt;- LZ_Factor(k, PSV, NSV)
 end while
其它.
LZ77算法通过使用编码器或者解码器中已经出现过的相应匹配数据信息，替换当前数据从而实现压缩功能。这个匹配信息使用称为"长度-距离对"的一对数据进行编码，它等同于“每个给定"长度"个字符都等于后面特定"距离"字符位置上的未压缩数据流。”（“距离”有时也称作“偏移”。）
编码器和解码器都必须保存一定数量的最近的数据，如最近2 KB、4 KB或者32 KB的数据。保存这些数据的结构叫作"滑动窗口"，因为这样所以LZ77有时也称作滑动窗口压缩。编码器需要保存这个数据查找匹配数据，解码器保存这个数据解释编码器所指代的匹配数据。所以编码器可以使用一个比解码器更小的滑动窗口，但是反过来却不行。
许多关于LZ77算法的文档都将长度距离对描述为从滑动窗“复制”数据的命令：“在缓冲区中回退"距离"个字符然后从那点开始复制"长度"个字符。”尽管对于习惯于指令式编程的人们来说很直观，但是它仍然使得人们很难理解LZ77编码的一个特点：也就是说，长度距离对中的长度超过距离这样一种情况不仅是可接受的而且还是经常出现的情况。作为一个复制命令，就会让人费解：“回退"一个"字符然后从那点开始复制"七个"字符。”但是如果缓冲区中只有一个字符的话那该如何复制七个字符呢？然而将长度距离对看作对于特性的描述就可以避免这种混淆：后面的七个字符与前面的七个完全相同。这就意味着每个字符都可以通过在缓冲区找到确定下来：即使在当前数据对解码开始的时候所要查找的字符并不在缓冲区中也可以。通过这个定义，这样的数据对将是"距离"字符序列的多次重复，也就是说LZ77成了一个灵活容易的行程长度编码形式。
尽管所有的LZ77算法都是根据同样的基本原理工作，但是如何输出编码后的数据可能会大不一样，例如长度与距离的取值范围是多少以及如何区分长度距离对和"字面符号"（即直接用字符本身，而不是用长度距离对表示）。下面是一些实例：
LZ78.
LZ77算法针对过去的数据进行处理，而LZ78算法却是针对后来的数据进行处理。LZ78通过对输入缓存数据进行预先扫描与它维护的字典中的数据进行匹配来实现这个功能，在找到字典中不能匹配的数据之前它扫描进所有的数据，这时它将输出数据在字典中的位置、匹配的长度以及找不到匹配的数据，并且将结果数据添加到字典中。
尽管在最初得到流行，但是后来LZ78的普及逐渐衰减，这可能是由于在刚LZ78出现的一些年份，一部分LZ78算法获得了美国专利保护。最流行的LZ78压缩形式是LZW算法，这个算法是泰瑞·卫曲所开发的一个LZ78变体。
参考文献.
&lt;/li&gt;&lt;/ul&gt;