无损压缩算法原理

压缩一般分为两个步骤,建模和编码。一个完美的模型可以描述数据流是如何产生的,相当于一个python类里面的generator。只需要这个generator就可以产生所有数据,从而大大降低需要传输的数据。例如 如果需要传输的数据序列是1 1 2 3 5 … 6765 ,那么可以用一个“斐波那契数”来精准表达这个传输的序列。在实际应用中,很难得到这样的精准模型,因此一般都是近似的为数据构建一个数学模型。例如英文文章可以认为是一个字典模型,我们只要有字典以及对应的编号,就能还原出信息;在编码步骤中,信息将会被映射到一个编码中。可以认为是一种字典的映射,为了保证解码时信息的还原,必须要求解码时的信息映射是一一对应的。前缀码是一种比较常用的编码方式。已经证明 [McEliece 1977],对于任何可以唯一解码的非前缀代码,都可以找到具有相同码字长度的前缀代码。

需要把无损压缩算法与我们平常使用的无损压缩格式区分开,因为无损压缩工具一般都是多种压缩算法重叠使用的,而不是单一的压缩算法。只是不同的工具侧重点不一样,一般压缩算法可以被描述成三元组< 压缩速率,解压速率,压缩率> 目前还没有哪种压缩算法能够达到三者同时最优。

编码技术

Huffman coding

霍夫曼编码是一种熵编码,这种编码方式就是需要知道每个字符(symbol)出现的频率(需要扫描所有数据一次),然后构建哈夫曼二叉树。构建过程就是每次挑选出现频率最低的两个节点作为树的左右子节点,并且将它们合并成新的节点,新节点频率是它两之和。构建好huffman树后,就可以进行huffman编码,从根节点开始,左边支路编码为0,右边支路编码为1。然后再扫描一次数据,得到最优编码。缺点是耗时长,需要扫描两遍数据。因此有相关动态的huffman编码工作来提升它的性能,可能压缩率会有所下降。

这个句子“this is an example of a huffman tree”中得到的字母频率来建构霍夫曼树。句中字母的编码和频率如图所示。编码此句子需要135 bit(不包括保存树所用的空间)

构建完霍夫曼树后,就根据霍夫曼树进行编码

字母 频率 编码
space 7 111
a 4 010
e 4 000
f 3 1101
h 2 1010
i 2 1000
m 2 0111
n 2 0010
s 2 1011
t 2 0110
l 1 11001
o 1 00110
p 1 10011
r 1 11000
u 1 00111
x 1 10010

而解码的过程则是需要根据霍夫曼树进行解码,因此可能需要额外传输霍夫曼树(除非使用公共的霍夫曼树)。

缺点:对于具有均匀概率分布的一组符号,霍夫曼编码是无效的。

Arithmetic Coding

算术编码是一种无损数据压缩方法,也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(0.0 ≤ n < 1.0)的小数n。这里以一个简单的例子来说明算术编码的原理

: 对一个简单的信号源进行观察,得到的统计模型如下:

  • 60%的机会出现符号 中性
  • 20%的机会出现符号 阳性
  • 10%的机会出现符号 阴性
  • 10%的机会出现符号 数据结束符

根据这个统计模型,编码器可以将当前的区间分为若干子区间,子区间长度与对应符号的概率成正比。当前要编码的符号对应的子区间成为下一步编码中的初始区间。

:对于前面提出的4符号模型:

  • 中性对应的区间是[0, 0.6)
  • 阳性对应的区间是[0.6, 0.8)
  • 阴性对应的区间是[0.8, 0.9)
  • 数据结束符对应的区间是[0.9, 1)

编码的过程其实就是确定最终小数所在的区间,解码过程则是根据小数所在的区间进行解码。

假设我们的信号是“中性 阴性 数据结束符”,接下来看看对这个信号进行算数编码的过程

首先初始区间是[0,1),第一个字符“中性”使得编码后的区间在[0,0.6),然后第二个字符“阴性”使得编码后的区间进一步在[0,0.6) 的 [0.8, 0.9]这个区间,也就是[0+0.6*0.8,0+0.6*0.9) = [0.48,0.54)这个区间。最后一个字符“数据结束符”使得整个区间进一步在[0.48,0.54)的[0.9,1) 这个区间,也就是[0.48+(0.54-0.48)*0.9,0.54)=[0.534, 0.540)这个区间。因此任意在这个区间内的数都可以还原这个信息。

在解码的过程中,假设我们拿到的数是0.538,首先他出现在初始区间[0, 0.6) 中,我们知道第一个符号肯定是“中性”,然后它又出现在[0,0.6)的[0.8, 0.9)这个区间中,我们知道第二个符号肯定是“阴性”,以此类推。

与霍夫曼编码相比,算术编码还能接受条件概率,即前面出现的字符会影响后面出现字符的概率这种情况。而原始的霍夫曼编码则是在符号之间相互独立、且分布不同时才能达到比较好的压缩效果。

Lempel-Ziv Codes

与霍夫曼编码相比,LZW编码法被视作将不同长度字符串以固定长的码编码(霍夫曼编码将固定长度字符用不同长度的码编码)。其优点在于此方法只需存储一个相当小的表格,即可存储资料还原时相对应的值,所以所需成本相对地低;然而,这种算法的设计着重在实现的速度,由于它并没有对数据做任何分析,所以并不一定是最好的算法(参考LZMA,LZ77)。

编码过程:

  1. 初始化字典使其包含所有字符串的单一字符(例如ascii码)
  2. 在字典中寻找配当前输入的最长字符串W
  3. 将该字符串对应的字典编码作为结果输出,并且将W从输入中移除
  4. 将W+W后面的那个字符添加到字典
  5. 返回第二步

解码过程

解码过程通过从编码后的输入中读取一个值,在字典中找到对应的项目并进行输出。这种编码方式并不需要传输字典,因为字典可以在解码过程中构造出来:当解码一个值并输出一个字符串后,解码器将它与后面值的字符串的第一个字符进行拼接,作为新的字典项目放入字典中。然后处理下一值,直到全部处理完。

举个例子:假设要编码的字符串是"TOBEORNOTTOBEORTOBEORNOT#"

编码过程如表

Current Sequence Next Char Output Extended Dictionary Comments
Code Bits
NULL T
T O 20 10100 27: TO 27 = first available code after 0 through 26
O B 15 01111 28: OB
B E 2 00010 29: BE
E O 5 00101 30: EO
O R 15 01111 31: OR
R N 18 10010 32: RN 32 requires 6 bits, so for next output use 6 bits
N O 14 001110 33: NO
O T 15 001111 34: OT
T T 20 010100 35: TT
TO B 27 011011 36: TOB
BE O 29 011101 37: BEO
OR T 31 011111 38: ORT
TOB E 36 100100 39: TOBE
EO R 30 011110 40: EOR
RN O 32 100000 41: RNO
OT # 34 100010 # stops the algorithm; send the cur seq
0 000000 and the stop code

解码过程如下

Input Output Sequence New Dictionary Entry Comments
Bits Code Full Conjecture
10100 20 T 27: T?
01111 15 O 27: TO 28: O?
00010 2 B 28: OB 29: B?
00101 5 E 29: BE 30: E?
01111 15 O 30: EO 31: O?
10010 18 R 31: OR 32: R? created code 31 (last to fit in 5 bits)
001110 14 N 32: RN 33: N? so start reading input at 6 bits
001111 15 O 33: NO 34: O?
010100 20 T 34: OT 35: T?
011011 27 TO 35: TT 36: TO?
011101 29 BE 36: TOB 37: BE? 36 = TO + 1st symbol (B) of
011111 31 OR 37: BEO 38: OR? next coded sequence received (BE)
100100 36 TOB 38: ORT 39: TOB?
011110 30 EO 39: TOBE 40: EO?
100000 32 RN 40: EOR 41: RN?
100010 34 OT 41: RNO 42: OT?
000000 0 #

在实际的应用中,很多压缩算法库会对lz编码后的结果再次进行huffman之类的编码。

压缩算法

LZ77 、LZ78以及LZO

lz77和lz78都是基于字典的编码,lz77则会在压缩过程中维护一个滑动窗口,因此解码的时候必须从输入的起始位置开始。从概念上讲,lz78允许在解压时随机访问输入,如果整个字典都预先知道的话。然而在实际应用中,字典是在编码和解码过程中构造的。前面介绍的lz coding就是lz78编码。这里主要讲下lz77的思想:对于重复出现的字符串,我们只需要用一对<offset,length> 的数字就可以表示。例如“Hello world, hello ketty.”,对于第二个hello,我么可以用<13, 5> 来表示,表示它前面13个字符开始的5个字符(即第一个hello)。但是也可能找不到前面匹配的字符,因此使用一个三元组来表示,如利用<0,0,D>表示D本身。

具体实现则是使用滑动窗口,维护一个search buffer 和 look-ahead buffer。search buffer+look-ahead buffer的长度就是滑动窗口的大小。search buffer就是用来查找前面出现的字符,因为lz77的编码思想就是将前面出现的字符的位置以及长度来替代当前的字符,所以对于很长的文本必须限定搜索空间,这个搜索空间就是search buffer。look-ahead buffer则包含当前字符的后面的连续字符。一般我们使用的gzip算法默认配置32KB的滑动窗口,其中绝大部分用于存储search buffer,固定长度存储look-ahead buffer (例如 262个字符)。像gzip格式实现的是lz77+huffman,这种组合就成为deflate压缩算法。

LZO则是控制hash表的大小,并且代码实现上使用宏来减少函数的调用开销,主要通过一些工程上的优化来使得压缩算法更快。

BWT

也被称为块排序压缩,是一个被应用在数据压缩技术(如bzip2)中的算法。该算法于1994年被Michael Burrows和David Wheeler在位于加利福尼亚州帕洛阿尔托的DEC系统研究中心发明[1]。它的基础是之前Wheeler在1983年发明的一种没有公开的转换方法。

当一个字符串用该算法转换时,算法只改变这个字符串中字符的顺序而并不改变其字符。如果原字符串有几个出现多次的子串,那么转换过的字符串上就会有一些连续重复的字符,这对压缩是很有用的。该方法能使得基于处理字符串中连续重复字符的技术(如MTF变换和游程编码)的编码更容易被压缩。

变换过程:

算法将输入字符串的所有循环字符串按照字典序排序,并以排序后字符串形成的矩阵的最后一列为其输出。

还原过程:

基于上述的BWT变换过程,以字符串“banana”为例,我们得到了变换结果annb$aa。其还原过程见以下过程:

1.1 基于原字符串矩阵的最后一列为annb$aa,我们进行该列进行排序,得到$aaabnn,并将其作为还原矩阵的第一列

1.2 经过1.1的转移、排序和组合,我们得到了7对邻接字符串:<a$> <na> <na> <ba> <$b> <an> <an>,将这7对邻接字符串进行排序后,得到<$b> <a$> <an> <an> <ba> <na> <na>,由此,我们得到了还原矩阵的第二列b$nnaaa

1.3 经过1.2的转移、排序和组合,我们得到了7对邻接字符串:<a$b> <na$> <nan> <ban> <$ba> <ana> <ana>,将这7对邻接字符串进行排序后,得到<$ba> <a$b> <ana> <ana> <ban> <na$> <nan>,由此,我们得到了还原矩阵的第三列abaan$n 依此类推

PPM

部分匹配预测 (PPM) 是一种基于上下文建模和预测的自适应统计数据压缩技术。 PPM 模型使用未压缩符号流中的一组先前符号来预测流中的下一个符号。 PPM 算法还可用于在聚类分析中将数据聚类为预测分组。例如,如果“compr”在前面出现了,则接下来出现“e”的概率就很高。PPM维护一个上下文信息来估计下一个输入字符的出现概率。算术编码就很适合这种情况,就如文中所提到的。详细来说,长的上下文将提升预估的概率,但是需要更多的时间。该方案一般有比较大的内存需求,因此具体的实现方案出现的并不多。具体实现在细节上差异会很大,目前比较知名的是PPMd,这个算法相关的介绍也比较少,因此我也没弄明白咋回事。它的应用场景却是存在的,就是WinRAR这个软件使用的就是PPMd算法。

参考

  • https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
  • https://zh.wikipedia.org/wiki/%E7%AE%97%E6%9C%AF%E7%BC%96%E7%A0%81
  • https://zh.wikipedia.org/wiki/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81
  • https://zh.wikipedia.org/wiki/Burrows-Wheeler%E5%8F%98%E6%8D%A2
  • https://en.wikipedia.org/wiki/Prediction_by_partial_matching
  • Barr, Kenneth C., and Krste Asanović. “Energy-aware lossless data compression.” ACM Transactions on Computer Systems (TOCS) 24.3 (2006): 250-291.

常用无损压缩算法原理简析相关推荐

  1. Android Handler与Looper原理简析

    一直感觉自己简直就是一个弱智,最近越来越感觉是这样了,真的希望自己有一天能够认同自己,认同自己. 本文转载于:https://juejin.im/post/59083d7fda2f60005d14ef ...

  2. Android V1及V2签名原理简析

    Android为了保证系统及应用的安全性,在安装APK的时候需要校验包的完整性,同时,对于覆盖安装的场景还要校验新旧是否匹配,这两者都是通过Android签名机制来进行保证的,本文就简单看下Andro ...

  3. Webpack模块化原理简析

    webpack模块化原理简析 1.webpack的核心原理 一切皆模块:在webpack中,css,html.js,静态资源文件等都可以视作模块:便于管理,利于重复利用: 按需加载:进行代码分割,实现 ...

  4. grpc通信原理_gRPC原理简析

    gRPC原理简析 gRPC是由谷歌提出并开发的RPC协议,gRPC提供了一套机制,使得应用程序之间可以进行通信. 降级开发者的使用门槛,屏蔽网络协议,调用对端的接口就像是调用本地的函数一样.而gRPC ...

  5. CRC原理简析——史上最清新脱俗简单易懂的CRC解析

    CRC原理简析 1. CRC校验原理 CRC校验原理根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端.当然,这个 ...

  6. Java的定时器Timer和定时任务TimerTask应用以及原理简析

    记录:272 场景:Java JDK自带的定时器Timer和定时任务TimerTask应用以及原理简析.在JDK工具包:java.util中可以找到源码,即java.util.Timer和java.u ...

  7. 转子接地保护原理_发变组转子接地保护原理简析

    发变组转子接地保护原理简析 发电机转子接地故障是常见的故障之一, 发生一点接地, 对发电机本身并不直接构成危 害,此时可通过转移负荷,平稳停机后,再查故障点:若在此基础上又发生另外一点接地, 将会严重 ...

  8. Mysql锁机制及原理简析

    Mysql锁机制及原理简析 一.前言 1.什么是锁? 锁是计算机协调多个进程或线程并发访问某一资源的机制. 锁保证数据并发访问的一致性.有效性: 锁冲突也是影响数据库并发访问性能的一个重要因素. 锁是 ...

  9. JTAG、SWD调试原理简析

    JTAG.SWD调试原理简析 STM32F10xxx使用CortexM-M3内核,该内核内含硬件调试模块,支持复杂的调试操作.硬件调试模块允许内核在取指(指令断点)或访问数据(数据断点)时停止.内核停 ...

最新文章

  1. 香港深水埗一街道新春气氛浓厚
  2. oracle plsql异常,【Oracle篇】异常处理和PLSQL
  3. 前端学习(3024):vue+element今日头条管理-关于默认子路由的问题
  4. 电脑远程服务_电脑远程维修专家在线服务
  5. 7个JavaScript在IE和Firefox浏览器下的差异写法
  6. Retrofit的初次使用
  7. 高级网络配置+Ipv6
  8. SSCOM下载及无法打开问题解决
  9. 不想下载那么多音乐软件?全网音乐在线听和下载
  10. 常用媒体查询以及手机横竖屏监听
  11. mysql median_MySQL中查询中位数?
  12. 什么是 Holdout Set?
  13. 什么手势使用电子计算机比较快,手势功能
  14. 华为测试工程师实习面试——技术面+综合面
  15. 男生哪个瞬间让你心疼?
  16. Java poi 导出excel 设置将整个工作表打印在一页 打印机设置
  17. Cisco交换机 ——STP
  18. 数据事务及ACID特性、事务回滚
  19. 【BSP视频教程】STM32H7视频教程第3期:整体捋顺STM32H7的HAL库和LL库的框架,再配合寄存器造轮子找到更适合自己的玩法(2022-01-21)
  20. Liam的操作系统学习历程(一):xv6 homework 9 Barriers

热门文章

  1. 【艾琪出品】-【计算机】《办公自动化基础》-韩伟颖(2002)南开离线作业学习资料
  2. 统治IT行业的定律-安迪-比尔定律
  3. 字体的分类图示——对网页设计很有益的图
  4. ios点击推送闪退_升级iOS14.1之后,我的6S崩溃了|ios|ios系统|手机|闪退
  5. 清华大学计算机刘云鹏,清华大学2017年在豫高招录取结束
  6. Pocket 2003,Windows Mobile 5,Windows Mobile 6的部分区别
  7. 微信 php 地图定位,微信公众号定位地图位置写入数据库,再显示地图的方法
  8. java使用jbarcode生成条形码
  9. c+primer 学习笔记 6
  10. 梦行扫码付(收银台条码支付 微信钱包条码支付 支付宝二维码支付 手机APP钱包支付 PHP扫码支付 )