注:这一节我照抄教科书的。因为在网上找到了电子资源,同时觉得教科书本书做的阐述就挺不错。因此直接采纳了。但是在lz77算法中有一点补充,用此风格字体标出了。
背景
有许多场合,开始时不知道要编码数据的统计特性,也不一定允许你事先知道它们的统计特性。因此,人们提出了许许多多的数据压缩方法,企图用来对这些数据进行压缩编码,在实际编码过程中以尽可能获得最大的压缩比。这些技术统称为通用编码技术。词典编码(dictionary encoding)技术就是属于这一类,这种技术属于无损压缩技术。
词典编码(dictionary encoding)的根据是数据本身包含有重复代码这个特性。例如文本文件和光栅图像就具有这种特性。词典编码法的种类很多,归纳起来大致有两类。
第一类词典法的想法是企图查找正在压缩的字符序列是否在以前输入的数据中出现过,然后用已经出现过的字符串替代重复的部分,它的输出仅仅是指向早期出现过的字符串的“指针”。
这里所指的“词典”是指用以前处理过的数据来表示编码过程中遇到的重复部分。这类编码中的所有算法都是以abraham lempel和jakob ziv在1977年开发和发表的称为lz77算法为基础的,例如1982年由storer和szymanski改进的称为lzss算法就是属于这种情况。
第二类算法的想法是企图从输入的数据中创建一个“短语词典(dictionary of the phrases)”,这种短语不一定是像“严谨勤奋求实创新”和“国泰民安是坐稳总统宝座的根本”这类具有具体含义的短语,它可以是任意字符的组合。编码数据过程中当遇到已经在词典中出现的“短语”时,编码器就输出这个词典中的短语的“索引号”,而不是短语本身。
j.ziv和a.lempel在1978年首次发表了介绍这种编码方法的文章。在他们的研究基础上,terry a.weltch在1984年发表了改进这种编码算法的文章,因此把这种编码方法称为lzw(lempel-ziv walch)压缩编码,首先在高速硬盘控制器上应用了这种算法。
LZ77算法
为了更好地说明LZ77算法的原理,首先介绍算法中用到的几个术语:
1.输入数据流(input stream):要被压缩的字符序列。
2.字符(character):输入数据流中的基本单元。
3.编码位置(coding position):输入数据流中当前要编码的字符位置,指前向缓冲存储器中的开始字符。
4.前向缓冲存储器(Lookahead buffer):存放从编码位置到输入数据流结束的字符序列的存储器。
5.窗口(window):指包含W个字符的窗口,字符是从编码位置开始向后数也就是最后处理的字符数。
6.指针(pointer):指向窗口中的匹配串且含长度的指针。
LZ77编码算法的核心是查找从前向缓冲存储器开始的最长的匹配串。编码算法的具体执行步骤如下:
1.把编码位置设置到输入数据流的开始位置。
2.查找窗口中最长的匹配串。
3.以“(Pointer, Length) Characters”的格式输出,其中Pointer是指向窗口中匹配串的指针,Length表示匹配字符的长度,Characters是前向缓冲存储器中的不匹配的第1个字符。
4.如果前向缓冲存储器不是空的,则把编码位置和窗口向前移(Length+1)个字符,然后返回到步骤2。
[例4.5] 待编码的数据流如表4-09所示,编码过程如表4-10所示。现作如下说明:
1.“步骤”栏表示编码步骤。
2.“位置”栏表示编码位置,输入数据流中的第1个字符为编码位置1。
3.“匹配串”栏表示窗口中找到的最长的匹配串。
4.“字符”栏表示匹配之后在前向缓冲存储器中的第1个字符。
5.“输出”栏以“(Back_chars, Chars_length) Explicit_character”格式输出。其中,(Back_chars, Chars_length)是指向匹配串的指针,告诉译码器“在这个窗口中向后退Back_chars个字符然后拷贝Chars_length个字符到输出”,Explicit_character是真实字符。例如,表4-10中的输出“(5,2) C”告诉译码器回退5个字符,然后拷贝2个字符“AB”
但wikipedia认为,粗体字这里理解成“从编码位置开始往回数Back_chars个字符,从该字符开始数起的字符串与接下来的Chars_length个字符完全相同”更加合适。
原文为“each of the next length characters is equal to the character exactly distance characters behind it in the uncompressed stream”
表4-09待编码的数据流
位置 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
字符 |
A |
A |
B |
C |
B |
B |
A |
B |
C |
表4-10 编码过程
步骤 |
位置 |
匹配串 |
字符 |
输出 |
1 |
1 |
-- |
A |
(0,0) A |
2 |
2 |
A |
B |
(1,1) B |
3 |
4 |
-- |
C |
(0,0) C |
4 |
5 |
B |
B |
(2,1) B |
5 |
7 |
A B |
C |
(5,2) C |
表4-13 编码字符串
位置 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
字符 |
A |
B |
B |
C |
B |
C |
A |
B |
A |
表4-14 编码过程
步骤 |
位置 |
词典 |
输出 |
1 |
1 |
A |
(0,A) |
2 |
2 |
B |
(0,B) |
3 |
3 |
B C |
(2,C) |
4 |
5 |
B C A |
(3,A) |
5 |
8 |
B A |
(2,A) |
与LZ77相比,LZ78的最大优点是在每个编码步骤中减少了缀-符串(String)比较的数目,而压缩率与LZ77类似。
表4-15 词典
码字(Code word) |
前缀(Prefix) |
1 |
|
… |
… |
193 |
A |
194 |
B |
… |
… |
255 |
|
… |
… |
1305 |
abcdefxyF01234 |
… |
… |
LZW编码器(软件编码器或硬件编码器)就是通过管理这个词典完成输入与输出之间的转换。LZW编码器的输入是字符流(Charstream),字符流可以是用8位ASCII字符组成的字符串,而输出是用n位(例如12位)表示的码字流(Codestream),码字代表单个字符或多个字符组成的字符串。
LZW编码器使用了一种很实用的分析(parsing)算法,称为贪婪分析算法(greedy parsing algorithm)。在贪婪分析算法中,每一次分析都要串行地检查来自字符流(Charstream)的字符串,从中分解出已经识别的最长的字符串,也就是已经在词典中出现的最长的前缀(Prefix)。用已知的前缀(Prefix)加上下一个输入字符C也就是当前字符(Current character)作为该前缀的扩展字符,形成新的扩展字符串——缀-符串(String):Prefix.C。这个新的缀-符串(String)是否要加到词典中,还要看词典中是否存有和它相同的缀-符串String。如果有,那么这个缀-符串(String)就变成前缀(Prefix),继续输入新的字符,否则就把这个缀-符串(String)写到词典中生成一个新的前缀(Prefix),并给一个代码。
LZW编码算法的具体执行步骤如下:
步骤1: 开始时的词典包含所有可能的根(Root),而当前前缀P是空的;
步骤2: 当前字符(C) :=字符流中的下一个字符;
步骤3: 判断缀-符串P+C是否在词典中
(1) 如果“是”:P := P+C // (用C扩展P);
(2) 如果“否”
① 把代表当前前缀P的码字输出到码字流;
② 把缀-符串P+C添加到词典;
③ 令P := C //(现在的P仅包含一个字符C);
步骤4: 判断码字流中是否还有码字要译
(1) 如果“是”,就返回到步骤2;
(2) 如果“否”
① 把代表当前前缀P的码字输出到码字流;
② 结束。
LZW编码算法可用伪码表示。开始时假设编码词典包含若干个已经定义的单个码字。例如,256个字符的码字,用伪码可以表示成:
Dictionary[j] ← all n single-character, j=1, 2, …,n
j ← n+1
Prefix ← read first Character in Charstream
while((C ← next Character)!=NULL)
Begin
If Prefix.C is in Dictionary
Prefix ← Prefix.C
else
Codestream ← cW for Prefix
Dictionary[j] ← Prefix.C
j ← n+1
Prefix ← C
end
Codestream ← cW for Prefix
2. 译码算法
LZW译码算法中还用到另外两个术语:①当前码字(Current code word):指当前正在处理的码字,用cW表示,用string.cW表示当前缀-符串;②先前码字(Previous code word):指先于当前码字的码字,用pW表示,用string.pW表示先前缀-符串。
LZW译码算法开始时,译码词典与编码词典相同,它包含所有可能的前缀根(roots)。LZW算法在译码过程中会记住先前码字(pW),从码字流中读当前码字(cW)之后输出当前缀-符串string.cW,然后把用string.cW的第一个字符扩展的先前缀-符串string.pW添加到词典中。
LZW译码算法的具体执行步骤如下:
步骤1:在开始译码时词典包含所有可能的前缀根(Root)。
步骤2:cW :=码字流中的第一个码字。
步骤3:输出当前缀-符串string.cW到码字流。
步骤4: 先前码字pW := 当前码字cW。
步骤5: 当前码字cW := 码字流中的下一个码字。
步骤6: 判断先前缀-符串string.pW是否在词典中
(1) 如果“是”,则:
① 把先前缀-符串string.pW输出到字符流。
② 当前前缀P :=先前缀-符串string.pW。
③ 当前字符C :=当前前缀-符串string.cW的第一个字符。
④ 把缀-符串P+C添加到词典。
(2) 如果“否”,则:
① 当前前缀P :=先前缀-符串string.pW。
② 当前字符C :=当前缀-符串string.cW的第一个字符。
③ 输出缀-符串P+C到字符流,然后把它添加到词典中。
步骤7: 判断码字流中是否还有码字要译
(1) 如果“是”,就返回到步骤4。
(2) 如果“否”, 结束。
LZW译码算法可用伪码表示如下:
Dictionary[j] ← all n single-character, j=1, 2, …,n
j ← n+1
cW ← first code from Codestream
Charstream ← Dictionary[cW]
pW ← cW
While((cW ← next Code word)!=NULL)
Begin
If cW is in Dictionary
Charstream ← Dictionary[cW]
Prefix ← Dictionary[pW]
cW ← first Character of Dictionary[cW]
Dictionary[j] ← Prefix.cW
j ← n+1
pW ← cW
else
Prefix ← Dictionary[pW]
cW ← first Character of Prefix
Charstream ← Prefix.cW
Dictionary[j] ← Prefix.C
pW ← cW
j ← n+1
end
[例4.7] 编码字符串如表4-16所示,编码过程如表4-17所示。现说明如下:
●“步骤”栏表示编码步骤;
●“位置”栏表示在输入数据中的当前位置;
●“词典”栏表示添加到词典中的缀-符串,它的索引在括号中;
●“输出”栏表示码字输出。
表4-16 被编码的字符串
位置 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
字符 |
A |
B |
B |
A |
B |
A |
B |
A |
C |
表4-17 LZW的编码过程
步骤 |
位置 |
词典 |
输出 |
|
(1) |
A |
|||
(2) |
B |
|||
(3) |
C |
|||
1 |
1 |
(4) |
A B |
(1) |
2 |
2 |
(5) |
B B |
(2) |
3 |
3 |
(6) |
B A |
(2) |
4 |
4 |
(7) |
A B A |
(4) |
5 |
6 |
(8) |
A B A C |
(7) |
6 |
-- |
-- |
-- |
(3) |
表4-18解释了译码过程。每个译码步骤译码器读一个码字,输出相应的缀-符串,并把它添加到词典中。例如,在步骤4中,先前码字(2)存储在先前码字(pW)中,当前码字(cW)是(4),当前缀-符串string.cW是输出(“A B”),先前缀-符串string.pW ("B")是用当前缀-符串string.cW ("A")的第一个字符,其结果("B A") 添加到词典中,它的索引号是(6)
表4-18 LZW的译码过程
步骤 |
代码 |
词典 |
输出 |
|
(1) |
A |
|||
(2) |
B |
|||
(3) |
C |
|||
1 |
(1) |
-- |
-- |
A |
2 |
(2) |
(4) |
A B |
B |
3 |
(2) |
(5) |
B B |
B |
4 |
(4) |
(6) |
B A |
A B |
5 |
(7) |
(7) |
A B A |
A B A |
6 |
(3) |
(8) |
A B A C |
C |
LZW算法得到普遍采用,它的速度比使用LZ77算法的速度快,因为它不需要执行那么多的缀-符串比较操作。对LZW算法进一步的改进是增加可变的码字长度,以及在词典中删除老的缀-符串。在GIF图像格式和UNIX的压缩程序中已经采用了这些改进措施之后的LZW算法。
LZW算法取得了专利,专利权的所有者是美国的一个大型计算机公司—Unisys(优利系统公司),除了商业软件生产公司之外,可以免费使用LZW算法。