📜  LZW (Lempel-Ziv-Welch) 压缩技术

📅  最后修改于: 2022-05-13 01:57:01.678000             🧑  作者: Mango

LZW (Lempel-Ziv-Welch) 压缩技术

为什么我们需要压缩算法?

压缩技术有两类,有损和无损。虽然每个都使用不同的技术来压缩文件,但两者都有相同的目标:在图形中查找重复数据(LZW 的 GIF)并使用更紧凑的数据表示。无损压缩通过识别和消除统计冗余来减少比特。无损压缩不会丢失任何信息。另一方面,有损压缩通过删除不必要或不太重要的信息来减少比特。所以我们需要数据压缩主要是因为:

  • 未压缩的数据会占用大量空间,这不利于有限的硬盘空间和互联网下载速度。
  • 虽然硬件变得更好、更便宜,但减少数据大小的算法也有助于技术的发展。
  • 示例:一分钟未压缩的高清视频可能超过 1 GB。我们如何才能在 25 GB 的蓝光光盘上放一部两小时的电影?

有损压缩方法包括 DCT(离散余弦变换)、矢量量化和变换编码,而无损压缩方法包括 RLE(运行长度编码)、字符串表压缩、LZW(Lempel Ziff Welch)和 zlib。存在几种压缩算法,但我们专注于 LZW。

什么是 Lempel-Ziv-Welch (LZW) 算法?

LZW 算法是一种非常常见的压缩技术。此算法通常用于 GIF 和可选的 PDF 和 TIFF。 Unix 的“压缩”命令,以及其他用途。它是无损的,这意味着压缩时不会丢失任何数据。该算法易于实现,并且在硬件实现中具有非常高的吞吐量的潜力。它是广泛使用的 Unix 文件压缩实用程序 compress 的算法,用于 GIF 图像格式。
Idea 依靠重复出现的模式来节省数据空间。 LZW 是最重要的通用数据压缩技术,因为它的简单性和多功能性。它是许多声称“将硬盘容量翻倍”的 PC 实用程序的基础。

它是如何工作的?

LZW 压缩的工作原理是读取一系列符号,将符号分组为字符串,并将字符串转换为代码。因为代码占用的空间比它们替换的字符串少,所以我们得到了压缩。 LZW 的特点包括:

  • LZW 压缩使用代码表,4096 作为表条目数的常见选择。代码表中的代码 0-255 总是被分配来表示来自输入文件的单个字节。
  • 编码开始时,代码表仅包含前 256 个条目,表的其余部分为空白。压缩是通过使用代码 256 到 4095 来表示字节序列来实现的。
  • 随着编码的继续,LZW 识别数据中的重复序列并将它们添加到代码表中。
  • 解码是通过从压缩文件中获取每个代码并通过代码表对其进行翻译以找到它所代表的字符字符实现的。

示例:ASCII 码。通常,每个字符都以 8 个二进制位存储,最多允许数据有 256 个唯一符号。该算法试图将库扩展到每个字符9 到 12 位。新的唯一符号由字符串中先前出现的符号组合组成。它并不总是能很好地压缩,尤其是对于短而多样的字符串。但对压缩冗余数据有好处,而且不必与数据一起保存新字典:这种方法既可以压缩数据,也可以解压缩数据。
已经有优秀的文章写出来了,你可以在这里更深入地看,Mark Nelson 的文章也值得称赞。

执行

压缩算法的思想如下:在处理输入数据时,字典保持最长遇到的单词和代码值列表之间的对应关系。单词被其对应的代码替换,因此输入文件被压缩。因此,算法的效率随着输入数据中长而重复的单词数量的增加而增加。

LZW 编码

*     PSEUDOCODE
  1     Initialize table with single character strings
  2     P = first input character
  3     WHILE not end of input stream
  4          C = next input character
  5          IF P + C is in the string table
  6            P = P + C
  7          ELSE
  8            output the code for P
  9          add P + C to the string table
  10           P = C
  11         END WHILE
  12    output code for P 

测试下面的代码:

eee

输出 :

费

此外,检查由 Mark Nelson 转换为 C++ 样式的代码。这里还有 6 个不同版本的另一种变体。此外,Rosettacode 列出了几种不同语言的 LZW 实现。

使用 LZW 压缩

示例 1:使用 LZW 算法压缩字符串: BABAABAAAA
下图系统地显示了所涉及的步骤。

LZW 算法

LZW 解压

LZW 解压器在解压过程中创建相同的字符串表。它从初始化为单个字符的前 256 个表条目开始。字符串表会针对输入流中的每个字符进行更新,第一个字符除外。解码是通过读取代码并通过正在构建的代码表翻译它们来实现的。

LZW 解压算法

*    PSEUDOCODE
1    Initialize table with single character strings
2    OLD = first input code
3    output translation of OLD
4    WHILE not end of input stream
5        NEW = next input code
6        IF NEW is not in the string table
7               S = translation of OLD
8               S = S + C
9       ELSE
10              S = translation of NEW
11       output S
12       C = first character of S
13       OLD + C to the string table
14       OLD = NEW
15   END WHILE

例2:LZW解压:使用LZW解压输出序列: <66><65><256><257><65><260>
下图系统地显示了所涉及的步骤。

d

在本例中,72 位用 72 位数据表示。在构建了合理的字符串表之后,压缩得到了显着提升。
LZW 总结:这个算法很好的压缩了重复的数据序列。由于代码字是 12 位,任何单个编码字符都会扩大而不是缩小数据大小。

用于编码和解码的 LZW 压缩的 C++ 代码如下所示:

C++
#include 
using namespace std;
vector encoding(string s1)
{
    cout << "Encoding\n";
    unordered_map table;
    for (int i = 0; i <= 255; i++) {
        string ch = "";
        ch += char(i);
        table[ch] = i;
    }
 
    string p = "", c = "";
    p += s1[0];
    int code = 256;
    vector output_code;
    cout << "String\tOutput_Code\tAddition\n";
    for (int i = 0; i < s1.length(); i++) {
        if (i != s1.length() - 1)
            c += s1[i + 1];
        if (table.find(p + c) != table.end()) {
            p = p + c;
        }
        else {
            cout << p << "\t" << table[p] << "\t\t"
                 << p + c << "\t" << code << endl;
            output_code.push_back(table[p]);
            table[p + c] = code;
            code++;
            p = c;
        }
        c = "";
    }
    cout << p << "\t" << table[p] << endl;
    output_code.push_back(table[p]);
    return output_code;
}
 
void decoding(vector op)
{
    cout << "\nDecoding\n";
    unordered_map table;
    for (int i = 0; i <= 255; i++) {
        string ch = "";
        ch += char(i);
        table[i] = ch;
    }
    int old = op[0], n;
    string s = table[old];
    string c = "";
    c += s[0];
    cout << s;
    int count = 256;
    for (int i = 0; i < op.size() - 1; i++) {
        n = op[i + 1];
        if (table.find(n) == table.end()) {
            s = table[old];
            s = s + c;
        }
        else {
            s = table[n];
        }
        cout << s;
        c = "";
        c += s[0];
        table[count] = table[old] + c;
        count++;
        old = n;
    }
}
int main()
{
 
    string s = "WYS*WYGWYS*WYSWYSG";
    vector output_code = encoding(s);
    cout << "Output Codes are: ";
    for (int i = 0; i < output_code.size(); i++) {
        cout << output_code[i] << " ";
    }
    cout << endl;
    decoding(output_code);
}


输出:
Encoding
String    Output_Code    Addition
W    87        WY    256
Y    89        YS    257
S    83        S*    258
*    42        *W    259
WY    256        WYG    260
G    71        GW    261
WY    256        WYS    262
S*    258        S*W    263
WYS    262        WYSW    264
WYS    262        WYSG    265
G    71
Output Codes are: 87 89 83 42 256 71 256 258 262 262 71 

Decoding
WYS*WYGWYS*WYSWYSG

LZW 相对于Huffman的优势

  • LZW 不需要有关输入数据流的先验信息。
  • LZW 可以一次压缩输入流。
  • LZW 的另一个优点是其简单性,允许快速执行。

资源:

  • 麻省理工学院
  • 戴夫·马歇尔
  • 杜克大学
  • 迈克尔·迪珀斯坦
  • LZW(优酷)
  • 教职员工.kfupm.edu.sa
  • github存储库(kmeelu)