C++ 中的 DEFLATE 压缩算法2025年5月15日 | 阅读 9 分钟 DEFLATE 是现代数据压缩的基石,巧妙地融合了两种关键算法的优势:LZ77(Lempel-Ziv 1977) 和 霍夫曼编码。它的强大之处不仅在于压缩率,还在于其在压缩速度和计算复杂性之间的平衡能力。让我们深入探讨 DEFLATE 的内部工作原理,阐明其理论基础和实际应用。 DEFLATE 的核心是通过一个多步骤过程来运作,首先是 LZ77 压缩。该算法由 Abraham Lempel 和 Jacob Ziv 于 1977 年构思,它采用滑动窗口方法来识别输入数据中的重复模式。通过将重复出现的序列替换为指向其先前出现的引用,LZ77 有效地减少了冗余。这些引用被称为 LZ77 对,包括距离(指示匹配在输入流中发生的回溯距离)和长度(表示重复序列的大小)。通过这种机制,LZ77 将输入数据转换为引用和字面字符的流。 LZ77 压缩LZ77 压缩,作为 数据压缩 的基础技术,通过在扫描输入流寻找重复序列时维护一个先前遇到数据的 滑动窗口 来运作。随着算法的进展,它会将当前的输入段与滑动窗口内的 子字符串 进行比较以识别匹配。 在找到匹配项后,LZ77 会将其编码为一个对,该对由滑动窗口内匹配序列的起始位置的距离和匹配序列的长度组成。此对有效地表示输入数据中的 重复模式。 通过有效地将重复模式编码为指向先前出现的引用,LZ77 通过避免冗余数据复制来实现压缩。此过程使 LZ77 能够在保持原始信息的同时减小 数据大小,使其成为各种 压缩算法 和 格式 的基本组成部分。 霍夫曼编码霍夫曼编码,一种关键的数据压缩技术,为输入字符分配可变长度的代码,更频繁的字符接收到更短的代码。该方法构建一个二叉树,其中每个叶节点代表一个不同的输入符号,从根到每个叶节点的路径会产生其相应的霍夫曼码。代码是根据符号频率确定的,更频繁的符号接收到更短的代码以优化压缩。 至关重要的是,霍夫曼码确保了唯一的解码性,这意味着没有任何一个码是另一个码的前缀。此属性确保了解码过程中的无歧义解释,从而促进无缝的压缩和解压缩过程。通过使用可变长度代码有效地表示输入符号,霍夫曼编码 在保持数据完整性的同时实现了可观的压缩率。 现在,我们将介绍 DEFLATE 如何结合这些技术 压缩DEFLATE 首先应用 LZ77 来查找输入数据中的重复序列。然后,它使用霍夫曼编码来编码 LZ77 生成的 字面符号(非重复数据)和 (距离,长度)对。 DEFLATE 为每个数据块构建一个 动态霍夫曼树,这使得它能够适应该块内符号的频率。此外,DEFLATE 可以使用静态霍夫曼码来表示常用符号,从而减少传输 霍夫曼树 的开销。 Deflate 过程DEFLATE 将输入数据划分为块,并独立处理每个块。对于每个块,DEFLATE 应用 LZ77 查找匹配项,然后根据该块中 符号的频率 构建霍夫曼树。 压缩数据由一系列块组成,每个块前面都有一个指定压缩方法和其他参数的头。DEFLATE 还包含高效存储 霍夫曼树 的机制,使用动态和静态表示。 解压缩解压缩涉及 反转 压缩过程。解压缩器读取压缩数据,根据头信息和数据重建霍夫曼树,然后使用这些树将压缩的符号解码回其原始形式。 LZ77 对 被解码以通过复制先前解码数据的片段来恢复原始数据。在 C++ 中实现 DEFLATE 涉及编码压缩和解压缩过程,包括 LZ77 压缩、霍夫曼编码以及处理块处理和头解析的算法。 程序输出 Compressed data: 100000000000000000100011 说明提供的代码为在 C++ 中实现 DEFLATE 压缩算法提供了一个基础结构。DEFLATE 是 LZ77 压缩 和 霍夫曼编码 的结合,是最有影响力的压缩技术之一,为 gzip、zlib 和 PNG 等广泛使用的格式提供支持。理解代码中每个组件的复杂性对于掌握 DEFLATE 压缩的本质至关重要。
包含像 <iostream>、<string>、<vector>、<map>、<bitset> 和 <sstream> 这样的标准库头文件为利用基本功能奠定了基础。这些头文件为程序提供了输入/输出操作、字符串操作、数据存储和处理以及位级操作的工具。
LZ77Pair 结构体体现了 LZ77 格式中压缩数据的基本单元。它 包含距离、长度和 nextChar,封装了 LZ77 压缩的本质。distance 表示匹配字符串先前出现位置的偏移量,length 表示匹配字符串的长度,nextChar 表示匹配段之后的紧邻字符。
compress_with_lz77 函数作为实现 LZ77 压缩算法的占位符。本质上,LZ77 在输入数据中寻找重复模式,并用指向先前出现的引用的方式替换它们。通过识别和利用冗余,LZ77 实现压缩。 同样,generate_huffman_codes 函数为霍夫曼编码奠定了基础。霍夫曼编码根据输入符号的频率为其分配可变长度的代码,从而通过为更频繁的符号分配更短的代码来优化压缩。
divide_into_blocks 函数有效地将输入数据分段为固定大小的块,从而简化了大型数据集的压缩过程。通过将输入分解为可管理的块,基于块的处理提高了算法的效率和可扩展性。每个块都经过独立压缩,允许并行处理和简化处理。 这种方法优化了内存使用,并实现了更快的压缩和解压缩时间。最后,基于块的处理降低了处理大量数据的复杂性,使压缩算法更健壮,并能适应各种用例。
generate_header 函数在 DEFLATE 压缩中起着关键作用,为 单个块 创建头。这些头包含关键元数据,例如块大小和压缩方法。通过在每个头中包含基本信息,该函数可确保在解压缩过程中正确解释。头充当重要的 组织工具,促进压缩数据的连贯处理。 它们为解压缩算法提供了必要的上下文,以从压缩块高效地重建原始数据。最后,generate_header 函数通过实现压缩和 解压缩过程 之间的无缝通信,为 DEFLATE 压缩 算法的完整性和有效性做出了贡献。 主函数作为程序的入口点,main 函数负责整个压缩过程。它初始化输入数据,将其划分为块,使用 LZ77 和霍夫曼编码压缩每个块,生成头,并将压缩数据聚合起来。最后,它将压缩数据输出到控制台。 本质上,该代码为实现 DEFLATE 压缩奠定了基础,为集成 LZ77 压缩和霍夫曼编码算法提供了占位符。通过理解每个组件的角色和交互,开发人员可以更深入地研究 DEFLATE 压缩的复杂性,并探索优化和改进的途径。此外,探索实际实现并分析跨不同数据集的压缩性能可以丰富理解,并促进数据压缩方法的创新。 复杂度分析时间复杂度 LZ77 压缩 (compress_with_lz77 函数 O(n^2) ) 在最坏的情况下,当输入数据没有重复模式时,算法需要迭代输入字符串中的每个字符,并在滑动窗口中搜索匹配项。这导致了二次时间复杂度。 可以使用更高效的数据结构(如哈希映射或后缀树)来存储和搜索模式,从而优化复杂性,将搜索时间减少到 O(n log n) 甚至 O(n)。 霍夫曼编码 (generate_huffman_codes 函数 O(n logn) ) 霍夫曼编码算法通常涉及从输入字符串中字符的频率构建霍夫曼树,然后根据字符在树中的位置为其分配 可变长度 代码。 构建霍夫曼树涉及按频率对字符进行排序,使用快速排序或归并排序等高效排序算法需要 O(n log n) 时间。 遍历霍夫曼树为字符分配代码需要 线性时间 O(n),因为树中的每个字符只访问一次。 块划分 (divide_into_blocks 函数 O(n) ) 将输入字符串划分为固定大小的块涉及一次迭代输入字符串并提取固定长度的子字符串。此操作具有与输入字符串大小成正比的线性时间复杂度。 主函数的最终时间复杂度取决于其组成操作的时间复杂度的总和。在这种情况下,主导因素是 LZ77 压缩和霍夫曼编码。 考虑到 LZ77 压缩和霍夫曼编码是主要操作,由于 LZ77 压缩的二次复杂度,总体时间复杂度为 O(n^2)。但是,通过优化的实现和高效的数据结构,总体时间复杂度可以降低到 O(n log n) 或 O(n)。 空间复杂度 LZ77 压缩 (compress_with_lz77 函数 O(n) ) LZ77 压缩的空间复杂度主要取决于输入字符串的大小和存储压缩数据所需的存储空间。 由于 LZ77 算法生成指向模式先前出现的引用,而不是存储模式本身,因此空间复杂度通常与输入字符串的大小成正比。 霍夫曼编码 (generate_huffman_codes 函数 O(n) ) 霍夫曼编码的空间复杂度取决于输入字符串的大小以及存储 霍夫曼树 和 代码分配 所需的存储空间。 虽然构建霍夫曼树可能需要与输入字符串中唯一字符数量成比例的额外内存,但总体空间复杂度 仍然是线性的。 块划分 (divide_into_blocks 函数 O(1) ) 块划分的空间复杂度是常数,因为它不需要与输入大小成比例的额外内存分配。它只涉及创建固定大小的 子字符串。 主函数的最终空间复杂度由其组成操作的空间复杂度的总和决定,这主要由 LZ77 压缩和 霍夫曼编码 所支配。 考虑到 LZ77 压缩和霍夫曼编码,最终的空间复杂度为 O(n),其中 n 是输入字符串的大小。这是因为这两种操作都需要与输入字符串大小成比例的额外存储空间来存储压缩数据和霍夫曼码。 下一主题C++ 三角火柴棍数字程序 |
在本文中,我们将讨论 C++ 中的 Concepts 和 Type Traits 之间的区别。在讨论它们的区别之前,我们必须了解 Concepts 和 Type Traits 的语法和示例。Concepts:C++20 Concepts 是功能强大的工具,具有广泛的适用性,主要用于简化...
阅读 13 分钟
正整数,例如具有特定除数关系的成对正整数的条目,被称为婚约数或准亲和数。一对数 a 和 b 被认为是婚约数,如果满足以下条件:σ(a) - a...
阅读 12 分钟
引言:完美欧拉函数数是一个正整数 n,使得 n 的迭代欧拉函数(包括 n 本身)之和等于 n。这个概念将欧拉函数 (ϕ(n)) 与直到值减小到 1 的迭代结果求和的思想结合起来。...
阅读 4 分钟
在当今动态的工作场所,有效的计划和时间管理对于保证生产力和促进团队合作至关重要。当团队同时处理多个项目、轮班甚至不同时区时,安排固定的时间非常具有挑战性……
11 分钟阅读
概述 C++20 标准引入了 source_location,这是一个用于确定源代码详细信息的实用工具,包括文件名、函数名、行号和列号。它的主要应用是在与程序相关的调试、日志记录和诊断过程中...
7 分钟阅读
C++ 和 C# 都是常见的编程语言,它们都提供独特的特性,用于不同的用例。C++ 是一种面向对象的、中级语言,主要用于系统级编程、游戏开发和关键应用程序。另一方面,C#...
5 分钟阅读
在本文中,我们将讨论 C++ 中的二维网格移位及其示例。引言:在 C++ 中,移动二维网格意味着将其每个组件沿预定方向(垂直或水平)移动。许多计算任务,包括图像处理、矩阵操作和基于网格的算法,经常...
5 分钟阅读
简介 C++ 中的 std::strided_slice 函数是一个概念,它指向在容器(例如数组或向量)中处理和操作特定元素时频繁使用的操作。步幅表示选择的元素之间的间隔有多远...
阅读 8 分钟
Std::move_only 是一种在 C++ 中引入的对象类型,它只能移动(不允许复制)。这种类型与 std::functionality 类似。Web 将能够通过链接计算各种实体提供的内容之间的含义。但是,移动构造函数是...
阅读 4 分钟
链表是计算机科学和编程语言中的基本数据结构,几乎出现在所有类型的计算机系统中。它与数组不同,因为它是动态的,并且通过组合顺序...
7 分钟阅读
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India