1. 引言

【数据压缩】LZ77算法原理及实现

【数据压缩】LZ78算法原理及实现

LZ77算法是采用字典做数据压缩的算法,由以色列的两位大神Jacob Ziv与Abraham Lempel在1977年发表的论文《A Universal Algorithm for Sequential Data Compression》中提出。

基于统计的数据压缩编码,比如Huffman编码,需要得到先验知识——信源的字符频率,然后进行压缩。但是在大多数情况下,这种先验知识是很难预先获得。因此,设计一种更为通用的数据压缩编码显得尤为重要。LZ77数据压缩算法应运而生,其核心思想:利用数据的重复结构信息来进行数据压缩。举个简单的例子,比如

取之以仁义,守之以仁义者,周也。取之以诈力,守之以诈力者,秦也。

取之以仁义守之以诈力均重复出现过,只需指出其之前出现的位置,便可表示这些词。为了指明出现位置,我们定义一个相对位置,如图

相对位置之后的消息串为取之以诈力,守之以诈力者,秦也。,若能匹配相对位置之前的消息串,则编码为以其匹配的消息串的起始与末端index;若未能匹配上,则以原字符编码。相对位置之后的消息串可编码为:[(1-3),(诈力),(6),(7-9),(诈力),(12),(6),(秦),(15-16)],如图所示:

上面的例子展示如何利用索引值来表示词,以达到数据压缩的目的。LZ77算法的核心思想亦是如此,其具体的压缩过程不过比上述例子稍显复杂而已。

2. 原理

本文讲主要讨论LZ77算法如何做压缩及解压缩,关于LZ77算法的唯一可译、无损压缩(即解压可以不丢失地还原信息)的性质,其数学证明参看原论文[1]。

滑动窗口

至于如何描述重复结构信息,LZ77算法给出了更为确切的数学解释。首先,定义字符串\(S\)的长度为\(N\),字符串\(S\)的子串\(S_{i,j},\ 1\le i,j \le N\)。对于前缀子串\(S_{1,j}\),记\(L_i^j\)为首字符\(S_{i}\)的子串与首字符\(S_{j+1}\)的子串最大匹配的长度,即:

\[L_i^j = \max \{ l | S_{i,i+l-1} = S_{j+1,j+l} \} \quad \text{subject to} \quad l \le N-j
\]

我们称字符串\(S_{j+1,j+l}\)匹配了字符串\(S_{i,i+l-1}\),且匹配长度为\(l\)。如图所示,存在两类情况:

定义\(p^j\)为所有情况下的最长匹配的\(i\)值,即

\[p^j = \mathop {\arg \max }\limits_{i} \{ L_i^j \} \quad \text{subject to} \quad 1 \le i \le j
\]

比如,字符串\(S=00101011\)且\(j=3\),则有

  • \(L_1^j=1\),因为\(S_{j+1,j+1}=S_{1,1}\), \(S_{j+1,j+2} \ne S_{1,2}\);
  • \(L_2^j=4\),因为\(S_{j+1,j+1}=S_{2,2}\), \(S_{j+1,j+2} = S_{2,3}\),\(S_{j+1,j+3} = S_{2,4}\),\(S_{j+1,j+4} = S_{2,5}\),\(S_{j+1,j+5} \ne S_{2,6}\);
  • \(L_3^j = 0\),因为\(S_{j+1,j+1} \ne S_{3,3}\)。

因此,\(p^j = 2\)且最长匹配的长度\(l^j=4\). 从上面的例子中可以看出:子串\(S_{j+1,j+p}\)是可以由\(S_{1,j}\)生成,因而称之为\(S_{1,j}\)的再生扩展(reproducible extension)。LZ77算法的核心思想便源于此——用历史出现过的字符串做词典,编码未来出现的字符,以达到数据压缩的目的。在具体实现中,用滑动窗口(Sliding Window)字典存储历史字符,Lookahead Buffer存储待压缩的字符,Cursor作为两者之间的分隔,如图所示:

并且字典与Lookahead Buffer的长度是固定的。

压缩

用\((p,l,c)\)表示Lookahead Buffer中字符串的最长匹配结果,其中

  • \(p\)表示最长匹配时,字典中字符开始时的位置(相对于Cursor位置),
  • \(l\)为最长匹配字符串的长度,
  • \(c\)指Lookahead Buffer最长匹配结束时的下一字符

压缩的过程,就是重复输出\((p,l,c)\),并将Cursor移动至\(l+1\),伪代码如下:

Repeat:
Output (p,l,c),
Cursor --> l+1
Until to the end of string

压缩示例如图所示:

解压缩

为了能保证正确解码,解压缩时的滑动窗口长度与压缩时一样。在解压缩,遇到\((p,l,c)\)大致分为三类情况:

  • \(p==0\)且\(l==0\),即初始情况,直接解码\(c\);
  • \(p>=l\),解码为字典dict[p:p+l+1]
  • \(p<l\),即出现循环编码,需要从左至右循环拼接,伪代码如下:
for(i = p, k = 0; k < length; i++, k++)
out[cursor+k] = dict[i%cursor]

比如,dict=abcd,编码为(2,9,e),则解压缩为output=abcdcdcdcdcdce。

3. 实现

bitarray的实现请参看A Python LZ77-Compressor,下面给出简单的python实现。

# coding=utf-8

class LZ77:
"""
A simplified implementation of LZ77 algorithm
""" def __init__(self, window_size):
self.window_size = window_size
self.buffer_size = 4 def longest_match(self, data, cursor):
"""
find the longest match between in dictionary and lookahead-buffer
"""
end_buffer = min(cursor + self.buffer_size, len(data)) p = -1
l = -1
c = '' for j in range(cursor+1, end_buffer+1):
start_index = max(0, cursor - self.window_size + 1)
substring = data[cursor + 1:j + 1] for i in range(start_index, cursor+1):
repetition = len(substring) / (cursor - i + 1)
last = len(substring) % (cursor - i + 1)
matchedstring = data[i:cursor + 1] * repetition + data[i:i + last] if matchedstring == substring and len(substring) > l:
p = cursor - i + 1
l = len(substring)
c = data[j+1] # unmatched string between the two
if p == -1 and l == -1:
return 0, 0, data[cursor + 1]
return p, l, c def compress(self, message):
"""
compress message
:return: tuples (p, l, c)
"""
i = -1
out = [] # the cursor move until it reaches the end of message
while i < len(message)-1:
(p, l, c) = self.longest_match(message, i)
out.append((p, l, c))
i += (l+1)
return out def decompress(self, compressed):
"""
decompress the compressed message
:param compressed: tuples (p, l, c)
:return: decompressed message
"""
cursor = -1
out = '' for (p, l, c) in compressed:
# the initialization
if p == 0 and l == 0:
out += c
elif p >= l:
out += (out[cursor-p+1:cursor+1] + c) # the repetition of dictionary
elif p < l:
repetition = l / p
last = l % p
out += (out[cursor-p+1:cursor+1] * repetition + out[cursor-p+1:last] + c)
cursor += (l + 1) return out if __name__ == '__main__':
compressor = LZ77(6)
origin = list('aacaacabcabaaac')
pack = compressor.compress(origin)
unpack = compressor.decompress(pack)
print pack
print unpack
print unpack == 'aacaacabcabaaac'

4. 参考资料

[1] Ziv, Jacob, and Abraham Lempel. "A universal algorithm for sequential data compression." IEEE Transactions on information theory 23.3 (1977): 337-343.

[2] guyb, 15-853:Algorithms in the Real World.

【数据压缩】LZ77算法原理及实现的更多相关文章

  1. 【数据压缩】LZ78算法原理及实现

    在提出基于滑动窗口的LZ77算法后,两位大神Jacob Ziv与Abraham Lempel [1]于1978年又提出了LZ78算法:与LZ77算法不同的是LZ78算法使用树状词典维护历史字符串. [ ...

  2. 数据压缩算法---LZ77算法 的分析与实现

    LZ77简介 Ziv和Lempel于1977年发表题为“顺序数据压缩的一个通用算法(A Universal Algorithm for Sequential Data Compression )”的论 ...

  3. Bagging与随机森林算法原理小结

    在集成学习原理小结中,我们讲到了集成学习有两个流派,一个是boosting派系,它的特点是各个弱学习器之间有依赖关系.另一种是bagging流派,它的特点是各个弱学习器之间没有依赖关系,可以并行拟合. ...

  4. RSA算法原理

    一直以来对linux中的ssh认证.SSL.TLS这些安全认证似懂非懂的.看到阮一峰博客中对RSA算法的原理做了非常详细的解释,看完之后茅塞顿开,关于RSA的相关文章如下 RSA算法原理(一) RSA ...

  5. LruCache算法原理及实现

    LruCache算法原理及实现 LruCache算法原理 LRU为Least Recently Used的缩写,意思也就是近期最少使用算法.LruCache将LinkedHashMap的顺序设置为LR ...

  6. MySQL索引背后的数据结构及算法原理【转】

    本文来自:张洋的MySQL索引背后的数据结构及算法原理 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持 ...

  7. OpenGL学习进程(13)第十课:基本图形的底层实现及算法原理

        本节介绍OpenGL中绘制直线.圆.椭圆,多边形的算法原理.     (1)绘制任意方向(任意斜率)的直线: 1)中点画线法: 中点画线法的算法原理不做介绍,但这里用到最基本的画0<=k ...

  8. 支持向量机原理(四)SMO算法原理

    支持向量机原理(一) 线性支持向量机 支持向量机原理(二) 线性支持向量机的软间隔最大化模型 支持向量机原理(三)线性不可分支持向量机与核函数 支持向量机原理(四)SMO算法原理 支持向量机原理(五) ...

  9. 分布式缓存技术memcached学习(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...

随机推荐

  1. html2canvas插件对整个网页或者网页某一部分截图并保存为图片

    html2canvas能够实现在用户浏览器端直接对整个或部分页面进行截屏.这个脚本将当前页面渲染成一个canvas图片,通过读取DOM并将不同的样式应用到这些元素上实现.它不需要来自服务器任何渲染,整 ...

  2. 高性能网站架构设计之缓存篇(3)- Redis 的配置

    我们说Redis是一个强大的Key-Value存储系统,在前面我们已遇到了两个问题: 1.redis server 启动后,独占进程,能不能修改为后台服务呢? 2.redis server 服务是单线 ...

  3. .NET跨平台实践:用C#开发Linux守护进程(转)

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  4. Fedora23Server配置

    系统准备 启动网卡: sudo service network start 更新系统: sudo dnf update 远程管理: https://IP:9090/ Dnf使用: http://www ...

  5. Android setTag方法的key问题

    android在设计View类时,为了能储存一些辅助信息,设计一个一个setTag/getTag的方法.这让我想起在Winform设计中每个Control同样存在一个Tag. 今天要说的是我最近学习a ...

  6. [我给Unity官方视频教程做中文字幕]beginner Graphics – Lessons系列之摄像机介绍Cameras

    [我给Unity官方视频教程做中文字幕]beginner Graphics – Lessons系列之摄像机介绍Cameras 最近得到一些Unity官方视频教程,一看全是纯英文的讲解,没有任何字幕或者 ...

  7. 在ThoughtWorks工作这几年我学到了什么?

    不知不觉,从2012年5月1日加入ThoughtWorks到现在,已经3年有余了.时间过得很快,这三年多我干了很多事情,但仔细想想也没有什么特别值得一提的.在一个公司呆久了总觉得很多事情是理所当然的, ...

  8. Thrift架构~windows下安装和Hello World及编码引起的错误

    最近开始正式接触Thrift架构,很牛B的技术,它被apache收纳了,属于开源中的一员,呵呵. 概念: Thrift源于大名鼎鼎的facebook之手,在2007年facebook提交Apache基 ...

  9. Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫。 马尔可夫链,的原理attilax总结

    Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫. 马尔可夫链,的原理attilax总结 1. 马尔可夫过程1 1.1. 马尔科夫的应用 生成一篇"看起来像文章的 ...

  10. salesforce 零基础学习(二十二)Test简单使用

    本篇内容只是本人简单的mark开发中常出现的一些疑问,方便后期项目使用时奠定基础,如果对Test零基础童鞋,欢迎查看Test官方的使用介绍: https://help.salesforce.com/a ...