信息论与压缩
前一章已经包含了若干用于压缩的编码原则:传真编码、即时编码、熵的原则等。现在,我们将从理解压缩在理论上何以可能开始,对这些原则进行更深入、更全面的探讨,并给出在计算机科学以及日常生活中的音乐和视频存储与交换中常用的压缩码示例。
目标是改善每条消息的传输时间以及存储空间。为此,必须构建能够优化消息大小的码。
这里我们假设信道不受干扰(即编码是无噪声的);错误处理将在最后一章中研究。我们将构建编码技术,以选择高效码,并建立一个重要理论,用于量化消息中包含的信息,计算编码方案的最小大小,从而确定给定码的“价值”。
我们首先关注无损数据压缩,即压缩后解压缩不会修改原始文件。第一节描述了无损压缩的理论极限,第二节介绍了逼近这些极限的算法。然后第三节展示了表示形式的改变如何扩展这些预设边界,并应用于常用码。
然后,在本章末尾,我们介绍了几种用于图像或声音压缩的技术,这些技术通过建模人类感知,允许在视觉或听觉质量上有所损失。
练习2.1(不可能无损压缩所有文件)
- 大小恰好为N位的文件有多少种?
- 大小严格小于N位的文件有多少种?
- 请据此结论判断是否存在一种方法可以减小任意文件的大小。
解答在第295页
练习2.2(关于可无损压缩文件的稀少性)
证明:在N比特的文件中,当N > 20时,信息无损可压缩超过20比特的文件不到百万分之一。 解答在第295页。
2.1 信息论
信息论为压缩码提供了数学框架。回顾一下,字母表是构成消息的有限集,包含要编码的信息或已编码的信息。源消息的字母集合(数据压缩通常称为信源编码)称为信源字母表,而码字符的集合称为码字母表。例如,拉丁字母表是我们用来撰写本文本的字母集合,而{0, 1}是用于编写通过大多数数字信道传输的消息的字母表。
在某个字母表V上所有有限字符串的集合记为V⁺,信源字母表通过编码函数的像集是V⁺的一个子集,称为码字集合,或有时简称为码,尤其是在信息论中。
因此,某个字母表V上的码C是V⁺的一个有限子集。该码由构成消息的基本元素组成。m ∈ C的元素称为一个码字,其长度记为l(m)。该码的进制是V的基数。进制为2的码称为二进制码。
例如,C={0, 10, 110}是字母表V={0, 1}上的一个二进制码,其进制为2。
2.1.1 码的平均长度
在本节中,为了简化起见,并且由于二进制码在通信中的实际重要性,我们主要关注二进制码。然而,以下大多数结果可以应用于任何码。
由于所有码字并不总是具有相同的大小,因此需要使用一种依赖于出现频率的度量方法来评估将用于编码信源的消息长度。需要指出的是,信息源由一个字母表组成S和一个在S上的概率分布ℙ。对于信源𝒮=(S, ℙ)中的一个符号si,P(si)是符号si出现的概率。
设𝒮=(S, ℙ),其中S={s₁,…, sₙ},并设C是𝒮中的一个码,其编码函数为f(S通过f的像是C)。该码C的平均长度为
$$
l(C) = \sum_{i=1}^{n} l(f(s_i))P(s_i).
$$
例2.1 S={a, b, c, d}, ℙ=(1/2, 1/4, 1/8, 1/8), V={0,1}
如果C={f(a)= 00, f(b)= 01, f(c)= 10, f(d)= 11},该方案的平均长度是2。
如果C={f(a)= 0, f(b)= 10, f(c)= 110, f(d)= 1110},则该方案的平均长度为
$$
1 \times \frac{1}{2} + 2 \times \frac{1}{4} + 3 \times \frac{1}{8} + 4 \times \frac{1}{8} = 1.875。
$$
使用编码方案的平均长度来衡量其效率。
2.1.2 熵作为信息量的度量
我们正在接近信息论的基本概念。让我们考虑一个信源 𝒮=(S, ℙ),只知道该信源的概率分布,而希望对其行为的不确定性进行定量度量。例如,当信源 S中的符号数量较多时,这种不确定性更高;若某个符号出现的概率接近于 1,则不确定性较低;当概率分布为均匀分布时,不确定性达到最大值。
人们使用信源的熵来度量该信源发出的平均信息量。
例如,假设有一个公平的骰子,其数值只能通过与我们选择的一个数字进行比较来确定:需要多少个问题才能确定该骰子的值?如果采用二分法,则仅需⌈log₂(6)⌉ = 3个问题。现在假设这个骰子是不公平的:其中一个数字出现的概率为1/2的概率,其余五个数字每个出现的概率均为1/10的概率。如果第一个问题是“它是1吗?”,那么在一半的情况下,只需这个问题即可确定骰子的值;而对于另一半情况,则还需要三个附加问题。因此,所需的平均问题数为
$$
\frac{1}{2} \times 1 + \frac{1}{2} \times 4 = -\frac{1}{2}\log_2\left(\frac{1}{2}\right) - 5 \times \frac{1}{10}\log_2\left(\frac{1}{10}\right).
$$
实际上,通过注意到在2,3,4,5,6中确定正确值并不总是需要三个问题,仍有可能优化这一结果:如果使用二分法将这五种可能性划分为两组,即2,3 和 4,5,6,那么只需再提出两个附加问题即可确定是2或3。只有5和6需要三个问题才能区分。当抽取次数较多时,若提问方式并不总是以相同的方式划分集合,例如划分为2,3,5 和 4,6,交替地仅需两个问题,等等,仍有可能改进此方法。通过扩展这一推理,可以证明这五种可能性所需的平均问题数等于log₂(10)。
因此,这次掷骰子所包含的信息量(可轻易推广至任何信源)通过平均问题数直观地定义。
正式地,熵被定义为信源(S, ℙ), ℙ=(p₁,…,pₙ)的
$$
H(𝒮) = \sum_{i=1}^{n} p_i \log_2\left(\frac{1}{p_i}\right).
$$
这是一种基于概率律的不确定性度量,通常用骰子的例子来说明:考虑一个由投掷一个n面骰子所产生的随机变量(信源)。我们已经看到,当骰子是公平的时,该实验结果的不确定性比骰子不公平时更大。这可以表示为如下形式:对于所有p₁,…,pₙ,H(p₁,…,pₙ) ≤ H(1/n,…, 1/n) = log₂n,根据第一章中的性质1。
2.1.3 香农定理
信息论的基本定理被称为香农定理或无噪编码定理。
首先,我们在考虑无记忆信源时阐述该定理。
定理20 设𝒮为熵为H(𝒮)的无记忆信源。任何在大小为q的字母表V(即q = |V|)上的唯一可译码𝒮,其平均长度l满足
$$
l \geq \frac{H(𝒮)}{\log_2 q}.
$$
此外,存在一个在大小为q的字母表上唯一可解码的码𝒮,其平均长度l满足
$$
l < \frac{H(𝒮)}{\log_2 q} + 1.
$$
证明。 第一部分:设C=(c₁,…, cₙ)是某个大小为q的字母表上的一个唯一可译码,其词的长度分别为(l₁,…, lₙ)。若K=∑ₙᵢ₌₁ 1/qˡⁱ,那么,由克劳夫特定理(参见第69页)可得K ≤ 1。设(q₁,…, qₙ)满足对所有i= 1,…,n均有 qᵢ= q⁻ˡⁱ / K。于是对所有i有 qᵢ ∈ [0, 1],且∑ₙᵢ₌₁ qᵢ= 1,因此(q₁,…, qₙ)构成一个概率分布。应用吉布斯引理(参见第17页),可得
$$
\sum_{i=1}^{n} p_i \log_2 \left(\frac{q^{-l_i}}{K p_i}\right) \leq 0;
$$
换句话说,
$$
\sum_{i=1}^{n} p_i \log_2 \left(\frac{1}{p_i}\right) \leq \sum_{i=1}^{n} p_i l_i \log_2 q + \log_2 K.
$$
然而,由于log₂K ≤ 0,因此有H(𝒮) ≤ l × log₂ q;故得此结果。
第二部分:设lᵢ= ⌈log_q (1/pᵢ)⌉。由于∑ₙᵢ₌₁ 1/qˡⁱ ≤ 1(实际上,qˡⁱ ≥ 1/pᵢ),故存在一个在大小为q的字母表上的唯一可译码,其码字长度等于(l₁,…, lₙ)。该码的平均长度为l=∑ₙᵢ₌₁ pᵢ lᵢ。然后,利用上取整函数的性质可得log_q (1/pᵢ) + 1 > lᵢ,因此
$$
\sum_{i=1}^{n} p_i \log_2 \left(\frac{1}{p_i}\right) > \sum_{i=1}^{n} p_i l_i \log_2 q - \log_2 q.
$$
这可以写成H(𝒮) > log₂ q((∑ₙᵢ₌₁ pᵢ lᵢ) − 1) = log₂ q(l − 1)。这证明了该定理。
可以推导出𝒮的k次扩张的定理:
定理21 设𝒮为熵为H(𝒮)的无记忆信源。在大小为q的某个字母表上,对𝒮ᵏ的任何唯一可译码,其平均长度lₖ满足
$$
\frac{l_k}{k} \geq \frac{H(𝒮)}{\log_2 q}.
$$
此外,存在一个在大小为q的字母表上的唯一可译码𝒮ᵏ,其平均长度lₖ满足
$$
\frac{l_k}{k} < \frac{H(𝒮)}{\log_2 q} + \frac{1}{k}.
$$
证明. 该定理的证明是直接的:根据第21页性质2,有H(𝒮ᵏ)= k × H(𝒮)。◽
对于任何平稳信源,该定理可以表述如下:
定理22 对于任意熵为H(𝒮)的平稳信源𝒮,存在一种在大小为q的字母表上的唯一可译编码过程,其平均长度l可以任意接近其下界:
$$
\frac{H(𝒮)}{\log_2(q)}.
$$
理论上,可以找到一种无限接近该界(取决于信源的熵)的码。然而在实践中,如果该过程包括对信源扩展后的词进行编码,则显然受限于这些词的数量(|𝒮ᵏ| = |𝒮|ᵏ,这可能代表大量的词)。接下来,我们将看到几种编码过程以及它们与该理论界的关系。
2.2 统计编码
统计编码方案利用信源中每个字符的频率进行压缩,并通过为最频繁的字符分配最短的码字,使其接近熵。
2.2.1 霍夫曼算法
这些方案能够为无记忆信源𝒮=(S, ℙ)找到最佳编码。码字母表V的大小为q。
为了保证结果的最优性,必须检查q − 1是否整除 |S| − 1(以获得一棵局部完备的树)。否则,可以简单地在S中添加一些出现概率为零的符号,直到q− 1整除 |S| − 1为止。相应的码字(最长的那些)将不会被使用。
算法2.1 霍夫曼算法的描述
使用信源字母表S,可以构建一组与ℙ的概率相关联的孤立节点(图2.1)。
设pᵢ₁, · · ·, pᵢ_q为概率最低的q个符号。可以构建一棵树(采用与霍夫曼树相同的模型),其根节点是一个新节点,对应的概率为pᵢ₁ + · · · + pᵢ_q。该树的边连接到节点pᵢ₁,…, pᵢ_q。图2.2展示了当q= 2时此操作的一个示例。
)
然后,从最高层节点(根节点)中值最小的q个开始重新操作,直到得到一棵单棵树(每一步中,最高层节点的数量减少q − 1个元素),其叶节点为S中的元素。该方案中对应的码字是从根到叶节点路径所对应的词。
示例2.2 (信源编码) 需要对以下信源进行编码,其定义在V={0, 1}上:
| 符号 | 概率 |
|---|---|
| a | 0.35 |
| b | 0.10 |
| c | 0.19 |
| d | 0.25 |
| e | 0.06 |
| f | 0.05 |
该算法的连续步骤如图2.3所示。然后,构造出以下霍夫曼编码:
| 符号 | 码字 |
|---|---|
| a | 11 |
| b | 010 |
| c | 00 |
| d | 10 |
| e | 0111 |
| f | 0110 |
练习2.3
本练习介绍一些关于霍夫曼算法生成的码的效率的理论元素。设𝒮=(S, ℙ)为信源,其中S=(0, 1), P(0)= 0.99,且P(1)= 0.01。
- 计算𝒮的熵。
- 给出霍夫曼算法在三阶扩展𝒮³上生成的码。其压缩率是多少?
- 通过比较此处获得的压缩率与练习1.33中的压缩率,你对霍夫曼算法的效率有何看法?是否符合香农定理?
解答在第295页。
练习2.4(用抛硬币玩421游戏)
人们希望使用骰子进行游戏,但手头只有硬币。因此,本练习涉及使用两面均匀硬币对六面均匀骰子进行编码。
- 骰子的熵是多少?
- 提出一种编码算法。
- 计算该码的平均长度。
- 此编码是否最优?
第296页解答
2.2.1.1 霍夫曼算法是最优的
定理23 由霍夫曼算法生成的码在所有定义在V上的即时码中是𝒮最优的。
证明。 为了简化本证明中的符号表示,我们假设q= 2。然而,该结果可以在每一步进行推广。
人们知道即时码可以用霍夫曼树来表示。设A为表示最优码的树,H为表示由霍夫曼算法生成的码的树。
注意到在A中,不存在一个只有一个子节点且其叶节点包含码字的节点(事实上,这样的节点可以被其子节点替代,从而得到更优的码)。
此外,如果在A中两个码字c₁, c₂的出现概率p₁,p₂满足p₁ < p₂,,则表示c₁, c₂的叶节点的深度——l₁,l₂ ——满足l₁ ≥ l₂(事实上,否则可以通过在树中将c₁与c₂互换以获得更优的码)。
因此,可以假设A表示一个最优码,其中概率最低的两个词是两个“姐妹”叶节点(它们具有相同的父节点)。
然后,对A中叶节点的数量n进行归纳思考。当n = 1时,结论显然成立。
对于任意n ≥ 2,考虑对应于A中出现概率最低的词c₁,c₂的两个“姐妹”叶节点,其概率分别为p₁,p₂。根据霍夫曼构造原则,c₁和c₂在H中也是“姐妹”叶节点。接着定义H′=H⧵{c₁, c₂}。这是针对码C′= C⧵{c₁,c₂}∪{c}的霍夫曼编码方案,其中c是一个出现概率为p₁+p₂的词。根据递归原理,H′表示在C′上的最优即时码;因此其平均长度低于A′= A⧵{c₁,c₂}的平均长度。因此,根据前述初步分析,H的平均长度低于A的平均长度。◽
必须理解这一定理的真正含义:它并不是说霍夫曼算法在所有情况下都是编码信息的最佳方法,而是指当固定某个字母表V上无记忆信源𝒮的模型时,不存在比具有前缀性质的霍夫曼算法更高效的码。
在实际应用中(参见本章末尾),人们会选择一个适应码的信源模型。特别是,如我们在以下示例中所注意到的,可以从信源扩展中获得更高效的码。
设𝒮=(S, ℙ),S=(s₁,s₂), ℙ=(1/4, 3/4)。对于𝒮的霍夫曼编码立即给出s₁ → 0和s₂ → 1,其平均长度为1。对于𝒮²的霍夫曼编码(S², ℙ²)给出
s₁s₁ → 010
s₁s₂ → 011
s₂s₁ → 00
s₂s₂ → 1
其平均长度为
$$
3 \times \frac{1}{16} + 3 \times \frac{3}{16} + 2 \times \frac{3}{16} + \frac{9}{16} = \frac{27}{16} = 1.6875。
$$
因此,该码的平均长度为l = 1.6875并且,与𝒮上的码相比(𝒮²中的码字长度为2),l = 1.6875/2 = 0.84375,优于原始信源上的码。
仍然可以通过考虑信源𝒮³来改进此编码方案。也可以使用更优的模型来优化编码:通常某些符号的出现并非独立于信源之前发出的符号(例如文本的情况)。在这种情况下,出现概率是条件概率,存在一些模型(特别是马尔可夫模型)能够实现更优的编码。然而,这类方法并不能带来无限的改进。熵仍然是平均长度的一个阈值,低于该阈值不存在任何码。
练习2.5
有人希望对一个非均匀骰子的连续投掷结果(假设投掷次数无限)进行编码。信源的符号表示为(1,2,3,4,5,6),其出现概率律为(0.12, 0.15, 0.16, 0.17, 0.18, 0.22)。
- 该信源的熵是多少?
- 为该信源提出一个由霍夫曼算法生成的三元码(在包含三个数字的字母表上)。其平均长度是多少?此类三元码的最小平均长度是多少?
- 对于二进制码,回答同样的问题。
第296页解答
练习2.6(前一练习的延续)
霍夫曼算法构建的码将固定长度的源词编码为长度可变的码字。然而,存储该码的存储器结构有时要求码字具有固定长度。
因此,为了满足这一约束,需要对来自信源的长度可变的数字序列进行编码。
坦斯塔尔码是遵循此原则的最优码。以下是针对二进制字母表情况的构造方法。如果选定的码字长度为k,则需要找到信源中希望编码的2ᵏ数字序列。在开始时,候选集是长度为1的词的集合(此处为{1,2,3,4,5,6})。
在此集合中,考虑最可能的词(此处为6)。然后构建添加一个数字到该词后形成的所有可能序列,并用这些序列替换候选集中的该词(此处得到{1,2,3,4,5,61,62,63,64,65,66})。接着重复此操作,直到候选集的大小严格小于2ᵏ时停止(即在超过该值之前停止)。
- 在某个二进制字母表上,为该骰子构建长度为k = 4的坦斯塔尔码的码字。
- 使用坦斯塔尔码,“6664”序列对应的码字是什么?使用霍夫曼码呢?
- 对每个码字,计算信源每个字符所需的编码比特数。推导出坦斯塔尔码的平均长度每比特信源。
第297页解答
2.2.2 算术编码
算术编码是一种统计方法,通常优于霍夫曼编码!然而,我们已经知道霍夫曼编码是最优的,那么还能在哪方面进行优化呢?在算术编码中,每个字符可以用非整数个比特进行编码:不同大小的字符字符串被编码为单个整数或计算机中的实数。例如,如果某个字符出现的概率为0.9,则该字符编码的最优大小应为
$$
\frac{H}{\log_2 Q} = \frac{9/10 \log_2(10/9) + 1/10 \log_2(10/1)}{\log_2 10}.
$$
因此,最佳编码约为每字符0.14比特,而像霍夫曼这样的算法肯定会使用整整一个比特。因此,我们将了解算术编码如何使人能够做得更好。
2.2.2.1 浮点运算
算术编码的思想是使用区间来对字符进行编码。算术编码的输出是一个介于0和1之间的简单实数,其构建方式如下:为每个符号分配一个子区间,该子区间的大小等于该符号出现的概率。分配顺序无关紧要,只要编码和解码时保持一致即可。例如,以下表格给出了一个信源及其对应的区间。
| 符号 | 概率 | 区间 |
|---|---|---|
| a | 0.1 | [0, 0.1) |
| b | 0.1 | [0.1, 0.2) |
| c | 0.1 | [0.2, 0.3) |
| d | 0.2 | [0.3, 0.5) |
| e | 0.4 | [0.5, 0.9) |
| f | 0.1 | [0.9, 1) |
一条消息将通过选择一个位于包含该消息信息的边界区间的数字进行编码。
对于消息中的每个字符,通过分配对应于该字符的子区间来细化当前区间。例如,如果当前区间是[0.15, 0.19),并且正在编码字符b,则分配与[0.15, 0.19)相关的子区间[0.1, 0.2);因此,[0.15+(0.19 − 0.15) × 0.1 = 0.154, 0.15+(0.19 − 0.15) × 0.2 = 0.158)。
如果要编码的消息是“bebecafdead”,则得到实数区间 [0.15633504384, 0.1563354640)。计算步骤如表2.1所示。例如,我们选择“0.15633504500”。编码过程是非常简单,其大致流程可在算法2.2中说明。
表2.1 对“bebecafdead”的算术编码
| 符号 | 下界 | 上界 |
|---|---|---|
| b | 0.1 | 0.2 |
| e | 0.15 | 0.19 |
| b | 0.154 | 0.158 |
| e | 0.1560 | 0.1576 |
| c | 0.15632 | 0.15648 |
| a | 0.156320 | 0.156336 |
| f | 0.1563344 | 0.1563360 |
| d | 0.15633488 | 0.15633520 |
| e | 0.156335040 | 0.156335168 |
| a | 0.156335040 | 0.1563350528 |
| d | 0.15633504384 | 0.15633504640 |
算法2.2 算术编码
- 设置下界 ← 0.0
- 设置上界 ← 1.0
- 当还有符号需要编码时执行
- C ← 要编码的符号
- 设x,y为表2.1中对应于C的区间的边界
- 大小 ← 上界 − 下界
- 上界 ← 下界 + 大小 × y
- 下界 ← 下界 + 大小 × x
- 当结束
- return 上界
对于解码,只需确定“0.15633504500”所在的区间:即[0.1, 0.2)。因此,第一个字母是“b”。
然后,必须通过减去最小值并除以包含“b”的区间的大小(即 (0.15633504500 − 0.1)/0.1 = 0.5633504500)来考虑下一个区间。这个数字表明下一个值是“e”。解码过程的后续步骤在表2.2中给出,程序在算法2.3中描述。
备注3 请注意,算法2.3的停止条件r ≠ 0.0 只有在输入数是编码过程中获得的区间下界时才有效。否则,将返回区间内的任意元素,如前面的示例所示,此时必须向解码过程提供另一个停止条件。例如,这可以是
- 要解码的确切符号数量(这通常可以作为压缩文件中的头部给出)。
- 另一种可能性是在要编码的消息末尾添加一个特殊字符(例如EOF)。该特殊字符可被赋予最低的概率。
总之,编码会根据字符的概率逐步按比例缩小区间。解码则执行相反的操作,增大该区间的大小。
表2.2 “0.15633504500”的算术解码
| 实数 | 区间 | 符号 | Size |
|---|---|---|---|
| 0.15633504500 | [0.1,0.2[ | b | 0.1 |
| 0.5633504500 | [0.5,0.9[ | e | 0.4 |
| 0.158376125 | [0.1,0.2[ | b | 0.1 |
| 0.58376125 | [0.5,0.9[ | e | 0.4 |
| 0.209403125 | [0.2,0.3[ | c | 0.1 |
| 0.09403125 | [0.0,0.1[ | a | 0.1 |
| 0.9403125 | [0.9,1.0[ | f | 0.1 |
| 0.403125 | [0.3,0.5[ | d | 0.2 |
| 0.515625 | [0.5,0.9[ | e | 0.4 |
| 0.0390625 | [0.0,0.1[ | a | 0.1 |
| 0.390625 | [0.3,0.5[ | d | 0.2 |
算法2.3 算术解码
- 设r为需要解码的数字
- 当r ≠ 0.0 时
- 设C为表2.1中其对应的区间包含r的符号
- 打印C
- 设a,b为表2.1中对应于C的区间的边界
- 大小 ← b − a
- r ← r − a
- r ← r/大小
- 结束 While
2.2.2.2 整数算术
上述编码依赖于实数在计算机中表示时尾数的大小;因此,它可能不具备完全的可移植性。因此,与浮点运算不同,这种编码方式并未被采用。十进制数通常按照计算机字的比特数逐个生成。此时,使用整数算术比浮点算术更自然:不再使用浮点区间[0,1],而是使用形如[00000,99999]的整数区间。之后的编码方式相同。例如,根据表2.1中的频率,“b”的首次出现对应[10000,19999],然后“e”将对应[15000,18999]。
然后可以注意到,一旦区间的两个边界中最高位的数字相同,该数字便不再改变。因此,可以将其打印到输出中,并从表示边界的整数中减去它。这样就能仅对较小的边界数字进行操作。对于消息“bebecafdead”,首次出现的“b”得到[10000,19999],打印“1”并减去它;因此,区间变为[00000,99999]。接着,“e”得到[50000,89999];后续过程见表2.3。结果为156335043840。
解码几乎遵循相同的流程,只是每次同时读取固定数量的数字:在每一步中,找到包含当前整数的区间(从而确定字符)。然后,如果当前整数和边界之间最高位数字相同,则对整个值集进行移位,如表2.4所示。
表2.3 “bebecafdead”的整数算术编码
| 符号 | 下界 | 上界 | 输出 |
|---|---|---|---|
| b | 10000 | 19999 | 移位1 00000 99999 1 |
| e | 50000 | 89999 | |
| b | 54000 | 57999 | 移位5 40000 79999 5 |
| e | 60000 | 75999 | |
| c | 63200 | 64799 | 移位6 32000 47999 6 |
| a | 32000 | 33599 | 移位3 20000 35999 3 |
| f | 34400 | 35999 | 移位3 44000 59999 3 |
| d | 48800 | 51999 | |
| e | 50400 | 51679 | 移位5 04000 16799 5 |
| a | 04000 | 05279 | 移位0 40000 52799 0 |
| d | 43840 | 46399 | 43840 |
表2.4 整数算术解码“156335043840”
| 移位 | 整数 | 下界 | 上界 | 输出 |
|---|---|---|---|---|
| 15633 | >10000 | <19999 | b | |
| 1 | 56335 | 00000 | 99999 | |
| 56335 | >50000 | <89999 | e | |
| 56335 | >54000 | <57999 | b | |
| 5 | 63350 | 40000 | 79999 | |
| 63350 | >60000 | <75999 | e | |
| 63350 | >63200 | <64799 | c | |
| 6 | 33504 | 32000 | 47999 | |
| 33504 | >32000 | <33599 | a | |
| 3 | 35043 | 20000 | 35999 | |
| 35043 | >34400 | <35999 | f | |
| 3 | 5043 | |||
| # 信息论与压缩 |
2.2.2.3 实际应用
如果在整个编码过程中,两个最高有效位数字始终不相等,则此编码方案会遇到问题。限定区间边界的整数大小会增加,但由于任何机器的限制,其大小不能无限增加。当得到如[59992, 60007]这样的区间时,就会面临这一问题。再经过几次迭代后,区间将收敛至[59999, 60000],而不再打印其他内容!
为了解决这个问题,必须比较最高有效位数字,如果这些数字仅相差1,则还需比较接下来的数字。然后,如果接下来的数字是9和0,则必须进行移位,并记住移位发生在次高有效位上。
例如,[59123, 60456]被移位到[51230, 64569]¹;索引1表示移位发生在第二位数字。然后,当最高有效位变得相等时(在0和9进行了k次移位之后),需要打印该数字,后跟k个0或k个9。在十进制或十六进制格式中,必须存储一个附加位以指示收敛是向上还是向下;而在二进制格式中,此信息可以立即推导得出。
¹ 此处原文可能存在笔误,[59123, 60456]移位后应为[91230, 04569],但根据上下文,意指通过移位解决边界数字接近的问题。
练习2.7
在概率为(“a”: 4/10; “b”: 2/10; “c”: 4/10)的情况下,字符串“bbbbba”的编码过程如下:
| 符号 | 区间 | 移位 | 输出 |
|---|---|---|---|
| b | [40000; 59999] [48000; 51999] [49600; 50399] [49200; 50799]¹ [48400; 51599]² [48400; 49679]² | 移位 0 和 9:[46000; 53999]¹ 移位 0 和 9:[42000; 57999]² 移位 4:[84000; 96799] | 499,然后是 84000 |
| b | b | b | b |
使用相同的概率,解码字符串49991680。
第297页解答。
2.2.3 自适应编码
由霍夫曼树(在考虑信源扩展时)或算术编码所表示的编码方案是基于第一章中介绍的信源模型的统计编码。也就是说,这些编码方案依赖于对文件中字符频率的先验知识。此外,这些编码方案需要一个查找表(或树)来关联源词和码字,以便能够解压缩。当考虑信源扩展时,该表可能会变得非常大。
练习2.8
让我们考虑一个包含四个字符(“a”,“b”,“c”,“d”)的字母表,其概率律为(1/2, 1/6, 1/6, 1/6)。
- 给出该信源的静态霍夫曼算法的查找表。
- 假设字符 a、b、c 和 d 采用 ASCII码(8 比特)表示,存储该表所需的最小存储空间是多少?
- 若对信源进行三字符扩展,存储其查找表所需的存储空间是多少?
- 包含明文序列“aaa aaa aaa bcd bcd bcd”的 ASCII 文件的大小是多少?
- 使用静态霍夫曼编码对该序列进行 compress。给出压缩文件的总体大小。使用信源扩展是否有意义?
第297页解答。
实际上,存在动态变体(即时),可避免预先计算频率和查找表。这些变体通常用于压缩中。它们使用字符频率,并在符号出现时实时计算频率。
2.2.3.1 动态霍夫曼算法 – pack
动态霍夫曼算法能够通过单次读取输入流的方式即时压缩数据;与静态霍夫曼算法不同,该方法避免了对输入文件进行两次扫描(一次用于计算频率,另一次用于编码)。频率表随着文件的读取过程逐步建立;因此,每次读取一个字符时,霍夫曼树都会被修改。
Unix系统“pack”命令实现了这种动态算法。
2.2.3.2 动态压缩
压缩在算法2.4中进行了描述。假设要编码的文件包含二进制符号,这些符号以每次读取k比特的形式即时读取(k通常是一个参数);因此,这样的块称为一个字符”。在初始化阶段,定义一个符号化字符(后续用@表示),最初使用预定义符号进行编码(例如,ASCII码中的第257个字符)。在编码过程中,每次遇到一个新的未知字符时,使用@的码后跟新字符的k比特在输出上对该字符进行编码。然后将该新字符引入霍夫曼树中。
算法2.4 动态霍夫曼算法:压缩
- 设nb(c)为字符c的出现次数
- 用字符 @ 初始化霍夫曼树(AH),nb(@)← 1
- 当未到信源结尾时 执行
- 从信源读取一个字符c
- 如果 是 c的首次出现,则
- nb(c)← 0
- nb(@)← nb(@)+ 1
- 在输出中打印AH中@的码,后跟c。
- else
- 在 AH 中打印 c 的码
- 结束 如果
- nb(c)← nb(c)+ 1
- 使用新的频率更新 AH
- 当结束
为了构建霍夫曼树并更新它,需要统计每个字符的出现次数以及已在文件中读取的字符数量;因此,在每次读取一个字符时,可以知道从文件开头到当前字符为止每个字符的频率;因此,这些频率是动态计算的。
在写入一个码(即@的码、一个已知字符的码,或一个新字符的k未压缩的位)之后,将该字符的出现次数加1。根据频率的变化,每一步都更新霍夫曼树。
因此,该树用于压缩(和解压缩),但无需将其发送给解码器。
可能对于@的出现次数有几种选择:在算法2.4中,它表示不同字符的数量(这使得可以在开头使用较少的比特来表示@),但也可以采用接近零的常数值(在这种情况下,@所用的比特数会根据霍夫曼树的深度而变化,从而使最频繁的字符具有最短编码)。
2.2.3.3 动态解压缩
动态解压缩在算法 2.5 中进行了描述。在初始化时,解码器只知道一个单一的码,即 @ 的码(例如,0)。首先它读取 0,我们假设这是与 @ 相关联的码。它推断接下来的 k比特包含一个新字符。它将这些 k比特输出到其输出端,并使用这个新字符更新已包含 @ 的霍夫曼树。
请注意,编码器和解码器各自维护自己的霍夫曼树,但它们都使用相同的算法来根据已读取字符的出现次数(频率)更新它。因此,编码器和解码器分别计算出的霍夫曼树完全相同。
算法2.5 动态霍夫曼算法解压缩
- 设nb(c)为字符c的出现次数
- 用字符 @ 初始化霍夫曼树(AH),nb(@)← 1
- 当 未到编码消息末尾 执行
- 在消息中读取一个码字 c(直到到达AH中的叶节点)
- 如果 c=@那么
- nb(@)← nb(@)+ 1
- 读取 c消息中的 k比特并将其打印到输出中。
- nb(c)← 0
- else
- 打印 c在 AH 中对应的字符
- 结束 如果
- nb(c)← nb(c)+ 1
- 使用新的频率更新AH
- 当结束
然后,解码器读取下一个码字,并使用其霍夫曼树对其进行解码。如果该码字表示符号 @,则读取对应新字符的k比特,将其写入输出,并将该新字符引入其霍夫曼树中(此后,该新字符将与某个码相关联)。否则,该码字表示一个已知字符;通过其霍夫曼树,恢复该码字所对应字符的k比特,并将其写入输出。然后,将其刚刚写入的字符的出现次数增加 1(如果是新字符,则同时增加 @ 的出现次数),并更新霍夫曼树。
这种动态方法在估计频率方面比静态方法效率略低。编码消息可能会稍长一些。然而,它避免了存储树和频率表,而这通常会使最终结果更短。这解释了为什么该方法在实际的常用压缩工具中被广泛使用。
练习2.9
再次考虑序列“aaa aaa aaa bcd bcd bcd ”
- 该序列的动态霍夫曼编码是什么?
- 该序列三个字符扩展的动态霍夫曼编码是什么?
第298页的解答
2.2.3.4 自适应算术编码
刚刚为霍夫曼算法开发的自适应编码的基本思想如下:
- 在每一步中,当前编码对应于使用已知的出现次数来计算频率所得到的静态编码。
- 每一步之后,编码器会使用其刚刚接收到的新字符更新频次表,并根据该表构建一个新的静态编码。这
必须始终以相同的方式进行,以便解码器在其轮到时能够执行相同的操作。
这个想法在处理算术编码时也可以轻松实现:该码的构建主要基于与算术编码相同的模型,不同之处在于概率分布由编码器根据已经处理过的符号即时计算。解码器能够执行相同的计算,从而保持同步。自适应算术编码器采用浮点运算:在第一次迭代时,区间 [0, 1] 被划分为大小相同的段。
每次接收到一个符号时,该算法都会计算新的概率分布,并将该符号对应的区间划分为新的子区间。但这些新区间的长度现在对应于符号的更新后的概率。接收到的符号越多,所计算出的概率就越接近真实概率。与动态霍夫曼编码类似,由于无需预先发送频率表,因此不存在额外开销;对于可变信源,编码器能够动态地适应概率的变化。
算术编码不仅通常能提供更好的压缩效果,而且人们还应注意,尽管静态霍夫曼实现比静态算术编码实现更快,但在自适应算术编码情况下,情况通常恰恰相反。
2.3 熵缩减的启发式方法
在实际应用中,霍夫曼算法(或其变体)常与其他编码过程结合使用。这些过程在理论上并不总是最优的,但它们对目标文件的形式(即信源模型)做出一些合理的假设,以降低熵值,或去除那些被认为对数据使用无关紧要的信息。
这里给出了熵缩减的三个示例。其原则是通过某种可逆变换将一条消息转换为另一条消息,使得新消息具有较低熵,从而能够更高效地进行压缩。
因此,整个技术涉及使用一种预编码,负责在应用压缩之前减少熵。
2.3.1 游程编码 (RLE)
尽管统计编码充分利用了最频繁的字符,但它并未考虑这些字符在消息中的位置。如果同一字符连续出现多次,则直接编码其出现次数可能会更有用。
例如,在传输传真页面时,统计编码会使用较短的码字来编码0,但每个0都会被单独写出,这将使得该编码效率低于第一章中介绍的传真代码,尤其是在包含大面积白色区域的页面上。
游程编码(RLE)是传真代码的扩展。它逐个读取每个字符,但当连续遇到至少三个相同的字符时,会打印出特定的重复码,后跟重复字符和重复次数。例如,使用@作为重复字符,则字符串“aabbbcddddd@ee”将被编码为“@a2@b3c@d5@@1@e2”(如果遇到特殊字符,需要一个小技巧来使该编码成为即时编码)。
练习2.10
- 以下图像的游程编码是什么?
- 与传真代码相比是否有优势?
- 在具有16级灰度的5位块上,其编码是什么?这种编码的优势是什么?我们能否在此系统上进一步压缩?
- 对于 255级灰度(最多连续16个相同像素)的情况,同样提出上述问题。
- 当按列扫描图像时,同样提出上述问题。
第298页的解答
为了避免与特殊重复字符相关的问题——该字符必须编码为长度为1的重复——调制解调器使用一种称为MNP5的RLE变体。如果同一字符连续出现三次或更多次,则将这三个字符显示出来,并跟一个计数器,用于指示该字符的额外出现次数。显然,必须指定用于此计数器的固定比特数。当计数器达到其最大值时,后续内容将通过多个由相同三个字符后跟一个计数器的块进行编码。例如,字符串“aabbbcddddd”被编码为“aabbb0cddd2”。在这种情况下,如果一个包含n字节的字符串中有m次重复,且平均长度为L,则当计数器使用1字节编码时,压缩的优势为 (n−m(L−4))/n。因此,这种编码方案非常有用,例如在黑白图像中,具有相同颜色的像素通常并列排列。显然,可以在RLE方案的结果上进一步执行统计压缩。
2.3.1.1 传真码(续完)
让我们回到传真代码——即我们在介绍编码基础(第一章)开头所使用的那个代码——并将其补充完整,给出其完整的压缩原则。
让我们回顾一下,扫描得到的信息是一组由1728个字符组成的行上的黑白像素,对其执行游程编码。黑白像素交替出现;因此需要编码的消息是一个1到1728之间的数字序列。这些数字将根据以下原则用其对应的比特编码进行编码。假设我们有一个唯一可译码,它包含了0到63之间所有数字的编码值,以及若干大于63的数字的编码值,使得对于任意大于n的数字,表中存在某个较大的数m,满足 0 ≤ n − m < 64。因此,n次重复用两个连续的代码表示:m次重复,然后是n − m次重复。
图2.4 展示了一些表的摘录,这些表显示了用于编码白像素的数字。0到 63之间的数字称为终止白码,大于64的数字称为补充白码。
黑像素也存在相同的表,但具有不同的编码值,整个构成一个唯一可解码的码。
2.3.2 移至前端
另一种预计算是可能的,如果将ASCII字符即时转换为其在0到255之间的值,同时动态修改表的排序。例如,每当一个字符出现在信源中时,首先用其对应的值进行编码,然后将其移至列表前端:此后该字符将用0编码,其余所有字符则相应移位一个单位。这种“移至前端”方法使得接近0的代码多于接近255的代码,从而改变了熵。
例如,如果表为 (a,b,c,d,e,f),则字符串“aaaaffff”可被视为熵为 1 的信源。然后它被编码为“00005555”。该码(视为信源)的熵为 1,也等于信源的熵。然而,移至前端的码将是熵为 H= 7/8 log₂(8/7) + 1/8 log₂(8) ≈ 0.55 的“00005000”。该码本身是可压缩的;这就是我们所说的熵缩减。
图2.4 传真代码
在这些条件下,人们能够更高效地编码结果消息——从而也更高效地编码初始消息。
此方法的一种变体是将字符在列表中向前移动固定的字符数,而不是将其推至列表的第一个位置。因此,最频繁的字符将位于列表的前端附近。这一思想预示了下一节将介绍的自适应编码和基于字典的编码。
练习2.11
设A是由以下八个符号组成的字母表:A =(“a”, “b”, “c”, “d”, “m”, “n”, “o”, “p”)。
- 根据符号在字母表中的位置,将其与0到7之间的一个数字相关联。“abcddcbamnop-ponm”和“abcdmnopabcdmnop”对应的数字是什么?
- 使用“移至前端”技术对上述两个字符串进行编码。你注意到什么?
- 例如,使用霍夫曼算法对前两个数字进行编码需要多少比特?
- 对经过 “移至前端”处理后得到的两个数字进行编码需要多少比特?比较它们的熵。
- 将霍夫曼算法扩展到两个字符时,对最后一个数字的结果是什么?此时频率表的大小是多少?
- 执行“移至前端”后再进行某种统计编码的算法具有哪些特性?
- “前移k位”是“移至前端”的一种变体,其中字符仅向前移动k位,而不是移动到栈的前端。使用k = 1对上述两个字符串进行编码,然后使用k = 2进行编码,并比较它们的熵。
第299页的解答。
2.3.3 Burrows‐Wheeler变换(BWT)
迈克尔·伯罗斯和大卫·惠勒所提出的启发式方法的核心思想是对字符串中的字符进行排序,以使移至前端和游程编码达到最高效的压缩效果。显然,问题在于通常无法从已排序的字符串中恢复原始字符串!因此,其巧妙之处在于对字符串进行排序,并发送一个中间字符串——该字符串比原始字符串具有更低的熵——同时还能用于恢复原始字符串。例如,考虑字符串“COMPRESSED”。需要生成所有可能的移位,如图2.5所示,然后按照字母表和字典序对这些行进行排序。
因此,新矩阵的第一列F是源词中所有字母的排序后字符串。最后一列称为L。
只写出第一列和最后一列,因为它们是编码中唯一重要的部分。在图示和文本中,我们为重复的字符添加了索引;这些索引有助于更直观地理解
变换。然而,它们在解码算法中并未被考虑:实际上,L和F之间的字符顺序必须保持不变。
显然,为了计算F(第一列)和L(最后一列),并不需要存储整个移位矩阵:只需一个沿字符串移动的指针即可。因此这一第一阶段非常直接。
然而,如果只发送F,我们如何计算逆变换?
这里的解决方案是发送字符串L而不是F:即使L未排序,它仍然是从一个几乎相同的字符串的排序版本中获取的,仅向右移位了一个字母。因此,可以期望该方法保留排序的特性,并降低熵。解码的巧妙之处在于知道这个字符串,同时结合主索引(包含原始字符串第一个字符所在行的编号;在图 2.5中为4——考虑0到8之间的数字),从而恢复原始字符串。只需对L进行排序即可恢复F。然后计算一个变换向量H,用于表示L和F之间的索引对应关系,即按照F中的字符排序顺序,在L中每个字符的位置。对于图2.5,这得到H={4, 0, 8, 5, 3, 6, 2, 1, 7},因为C位于位置4——即L中的主索引,接着E₂在L中的位置为0,而E₁在位置8,依此类推。然后需要注意的是,将L和F按列排列时,在每一行中两个连续的字母必然在原始字符串中也是连续的: 事实上,由于移位操作,最后一个字母变为第一个字母,第一个字母变为第二个字母。这也可解释为对于所有j,都有L[H[j]]=F[j]成立。然后,只需沿着这一序列的字母对进行追踪,即可恢复原始字符串,如算法2.6所述。
练习2.12 (BWT代码分析)
- Burrows-Wheeler变换(BWT)的排序矩阵的最后一列L包含相同字符的块,因此L可以被高效压缩。然而,如果第一列F完全排序,它将能被更加高效地压缩。为何选择L而不是F作为编码消息?
- 让我们考虑字符串S = “sssssssssh”。计算L及其移至前端压缩。
- 实际实现:BWT在长度为n的长字符串S上是高效的。实际上,存储整个n × n置换矩阵是不可行的。事实上,只需对这些置换进行排序即可。为此,需要能够比较两个置换。
(a) 给出一个索引,使其能够标识并区分原始字符串上的置换。
(b) 给出一个算法,compare_permutations,其输入为字符串S和两个整数i和j,计算一个布尔函数。该算法必须根据按字母顺序排序所有置换得到的顺序,判断左移i次的字符串是否在左移j次的字符串之前。
(c) 确定计算BWT所需的存储空间。
(d) 如何从置换表计算L和主索引?
第299页的解答
因此,在BWT的输出中,通常会得到一个较低熵的字符串,非常适合进行移至前端操作,然后进行游程编码。压缩工具bzip2(如图2.6所示)在执行霍夫曼编码之前,就使用了这一系列熵缩减技术。在第2.4.2节中,我们将看到该技术是当今最高效的技术之一。
图2.6 bzip2
2.4 常见压缩码
至于压缩工具 bzip2,当前的实现结合了多种压缩算法。这使得能够充分利用所有这些算法的优势。
减少压缩效果不佳的情况数量。在最广泛使用的技术中,伦佩尔和齐夫基于字典的技术能够对完整的字符块进行枚举。
2.4.1 伦佩尔‐齐夫算法 和 gzip变体
伦佩尔和齐夫发明的算法是一种基于字典的压缩算法(也称为因子替换); 它通过将一串字符(称为因子)替换为该因子在某个字典中的索引这一较短的码来实现压缩。其思想是对完整的词进行熵缩减,而不是对单个字符进行。
例如,在句子“a good guy is a good guy”中,可以动态地为已出现的词分配数字;因此,编码结果变为“a,good,guy,is,1,2,3”。需要注意的是,与动态算法一样,基于字典的方法仅需对文件进行单次读取。该算法存在多种变体,其中伦佩尔‐齐夫1977(LZ77)和伦佩尔‐齐夫1978(LZ78)是公开可用的。
2.4.1.1 伦佩尔-齐夫1977(LZ77)
该算法由伦佩尔和齐夫于1977年发表,是首个基于字典的算法。作为一种高效的霍夫曼算法替代方案,它重新激发了压缩领域的研究。LZ77基于一个从左到右沿文本滑动的滑动窗口。该窗口分为两个部分:第一部分代表字典,第二部分(读取缓冲区)首先接触文本。
初始时,窗口的位置使得读取缓冲区位于文本上,而字典部分不在文本上。
在每一步中,算法在字典中查找在读取缓冲区开头重复出现的最长因子;该因子用三元组 (i,j,c) 编码,其中
- i是缓冲区起始位置与字典中重复位置之间的距离;
- j是重复的长度;
- c是缓冲区中与字典中对应字符不同的第一个字符。
在重复编码完成后,滑动窗口向右移动j+ 1个字符。当在字典中未找到重复时,则使用(0, 0,c)对导致差异的字符c进行编码。
练习2.13(使用LZ77编码重复)
- 使用LZ77对序列“abcdef abcdef abcdef abcdef”进行编码。
- 使用 LZ77和长度为3比特的滑动窗口对该序列进行编码。
第300页的解答。
LZ77 最著名的实现之一是 Lempel–Ziv–Markov 链算法(LZMA)库,该库结合了预期的使用可变大小字典(自适应)的 LZ77 压缩,后接整数算术编码。
2.4.1.2 LZ78
这是对后者的改进,它用一个跟随文本的指针和一个独立的字典来替代滑动窗口,在该字典中查找指针读取的因子。现在假设在编码过程中读取了字符串sc,其中s是字典中索引为n的字符串,而c是一个字符,使得字符串sc不在字典中。因此,在输出上写入数对 (n,c)。然后将字符c附加到因子编号n上,形成一个新的因子,并将其添加到字典中,以便在后续文本中作为参考使用。与LZ77相比,这里只编码两个信息,而不是三个。
2.4.1.3 莱姆佩尔-齐夫-韦尔奇(LZW)
这是由特里·韦尔奇提出的另一种 Lempel‐Ziv变体。LZW被优利斯公司申请了专利。它仅通过字典中的索引 n进行编码;此外,文件是逐位读取的。可能需要一个初始字典(例如, ASCII码表)。如算法2.7所述,压缩过程十分直接:将每个已知的字符组替换为其码,并在字典中添加由该字符组及其下一个字符组成的新组。
算法2.7 LZW: 压缩
- 设字符串 ← ∅为当前字符串
- 当信源未结束 执行
- 从信源读取字符 c
- 如果 字符串|c在字典中 则
- 字符串←字符串|c
- else
- 打印该字符串的码
- 将 字符串|c加入字典
- 字符串← c
- 结束 如果
- 当结束
解压缩稍微复杂一些,因为当同一字符串连续两次被重现时,必须处理一个特殊情况。算法 2.8 描述了这一机制:与压缩类似,首先使用相同的初始字典,并逐步填充字典,但由于需要考虑下一个字符,因此存在延迟。当同一字符串连续出现时,这种延迟会成为一个问题。实际上,在这种情况下,新的码在解压缩过程中是未知的。然而,由于这是唯一会出现此类问题的情况,可以注意到字符串开头部分是相同的,从而能够推断出该组的值。
下面的练习说明了这一原则。
练习2.14
使用给定的十六进制ASCII表(7比特)作为初始字典,对字符串“BLEBLBLBA”进行LZW压缩和解压缩。
算法2.8 LZW:解压缩
- 读取编码消息的1字节 prec
- 当未到编码消息末尾 时
- 读取编码消息的1字节 curr
- 如果 curr在字典中 则
- 字符串 ←字典中curr的翻译
- else
- 字符串 ←字典中前缀的翻译
- 字符串←字符串|c
- 结束 如果
- 打印 字符串
- 设c为字符串的第一个字符
- 设 word为字典中prec的翻译
- 在字典中添加 word|c
- 前缀← curr
- 当循环结束
不考虑上述特殊情况,解压结果。你会注意到什么? 第300页的解答。
已提出几种可能的变体:例如,Lempel‐Ziv‐Miller‐Wegman(LZMW),其中将“字符串+下一个单词”添加到字典中,而不是仅添加“字符串+下一个字符”;甚至还有 Lempel‐Ziv 所有前缀(LZAP),其中将“字符串+下一个单词”的所有前缀都添加到字典中。
Unix命令compress实现了LZ77。至于gzip工具,它基于后者的变体; 它使用两棵动态霍夫曼树:一棵用于字符串,另一棵用于出现次数之间的距离。某些字符串的两次出现之间的距离是有限的:当距离变得过大时,将从当前字符重新开始压缩,而与此前已进行的压缩无关。
这种特性意味着单个使用gzip压缩的文件与一系列压缩文件之间在结构上没有区别。因此,设f1.gz和f2.gz分别为两个信源文件f1和f2压缩后的结果:将这两个文件追加到一个名为f3.gz的第三个文件中。然后,当使用gunzip f3.gz解压该文件时,得到的结果文件的内容与f1和f2依次追加的内容相同。
练习2.15 (gzip)
- 使用LZ77对字符串“abbarbarbbabbarbaa”进行编码。
- 将前面的编码分割为三个字符串(距离、长度和字符),并应用gzip,其中@的动态霍夫曼编码被强制为单个比特1。此外,在gzip中,第一个@不会被忽略。
- 假设前面的字符串被分成两部分:“abbarbarb”和“babbarbaa”,分别存放在两个独立的文件“f1”和“f2”中。请给出“f1.gz”和“f2.gz”的内容,即“f1”和“f2”使用gzip压缩后的结果。
- 将两个文件“f1.gz”和“f2.gz”追加合并为一个文件“f3.gz”(例如,使用Unix命令cat f1.gz f2.gz> f3.gz),然后使用gunzip解压文件“f3.gz”。结果是什么?由此推断不忽略第一个@的重要性。
- 如何在gzip中添加压缩级别(例如gzip-1 xxx.txt’或gzip-9 xxx.txt’)?
第301页的解答
2.4.2 压缩算法的比较
在表2.5中,我们通过邮件文件(即包含文本、图像和二进制文件)的示例,比较了在Unix/Linux上常用工具的压缩率和编解码速度。
gzip 和 compress 程序——使用 Lempel‐Ziv 的变体——将在下一节中介绍。bzip2—— 在进行熵编码之前使用类似 BWT 的熵缩减方法——在第2.3.3.7节‐Zip中介绍
表2.5 在PIV 1.5GHz上使用多种算法对一个邮件文件(15.92兆:文本、图像、程序等) 进行压缩的比较
| 算法 | 压缩比率,% | 编码速度,s | 解码速度,s | 文件, Mo |
|---|---|---|---|---|
| 7‐Zip‐4.42 (LZMA+?) | 62.57 | 23.93 | 6.27 | 5.96 |
| RAR‐3.51 (?) | 61.07 | 14.51 | 0.46 | 6.20 |
| rzip‐2.1 ‐9 (LZ77+Go) | 61.09 | 9.26 | 2.94 | 6.20 |
| ppmd‐9.1 (预测) | 60.71 | 11.26 | 12.57 | 6.26 |
| bzip2‐1.0.3 (BWT) | 57.96 | 7.41 | 3.16 | 6.69 |
| gzip‐1.3.5 ‐9 (LZ77) | 51.77 | 2.00 | 0.34 | 7.68 |
| gzip‐1.3.5 ‐2 (LZ77) | 47.99 | 1.14 | 0.34 | 8.28 |
| WinZip‐9.0(LZW+?) | 51.55 | 5 | 5 | 7.72 |
| compress (LZW) | 41.31 | 1.11 | 0.32 | 9.34 |
| lzop‐1.01 ‐9(LZW+?) | 41.32 | 6.73 | 0.09 | 9.35 |
| lzop‐1.01 ‐2(LZW+?) | 32.54 | 0.45 | 0.09 | 10.74 |
| 压缩 (霍夫曼) | 30.21 | 0.33 | 0.27 | 11.11 |
LZ77 的变体 Lempel–Ziv–Markov‐chain 算法(LZMA),结合算术编码 以及若干附加的启发式方法,具体取决于输入文件的类型。WinZip 也根据 文件类型使用多种变体,并以 LZW 编码结束。rzip 用于大文件,因此使用 具有非常大滑动窗口的 LZ77。ppmd 使用 Markov 概率模型,根据已知的 前序字符来预测下一个字符。而 lzop 则采用一组称为 Lempel-Ziv-Oberhumer(LZO) 的算法,针对解压速度进行了优化:它能够有效 处理长匹配和长字面运行,因此在高度冗余数据上表现良好,同时对低可压 缩数据也有可接受的处理能力。最后,pack 实现了简单的霍夫曼编码。
综上所述,实际表现反映了理论:LZ77 提供的压缩率优于 LZW,但速度较慢。基于字典的算法比简单的熵编码更高效,但不如采用良好启发式方法来降低熵的熵编码高效。通用软件通常会尝试多种方法以找出最佳压缩方式。尽管压缩率得到了提升,但压缩时间通常因此而受到影响。
2.4.3 GIF和PNG格式用于图像压缩
常见的紧凝数据格式会考虑待编码数据的性质。根据数据是声音、图像还是 文本,所采用的格式有所不同。在图像格式中,图形交换格式(GIF)是由 Compuserve公司提出的一种图形位图文件格式。它是一种针对像素图像的 压缩格式,即针对以点阵(像素)序列形式存储的图像;每个像素都有其颜色值。
压缩原则分为两个步骤。首先,将像素颜色(最初为24比特红/绿/蓝 (RGB) 编码的1680万种颜色)限制在2–256色的调色板范围内(即2、4、8、16、32、64、128或默认值256种颜色)。因此,每个像素的颜色会被四舍五入为调色板中最接近的颜色。当使用256色调色板保留大量不同颜色时,这可实现3倍的压缩比。然后,使用动态压缩算法Lempel‐Ziv‐Welch
1615

被折叠的 条评论
为什么被折叠?



