LZ77算法原理及python实现

本文深入解析LZ77压缩算法的工作原理,包括基本思想、算法流程、编码与解码过程,以及通过示例代码展示如何实现LZ77算法。适合对数据压缩技术感兴趣的读者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LZ77压缩算法

基本思想

众所周知,人类的本质就是复读机。

人们会再不知不觉中说出很多重复的话,有些基本的词句出现的频率也远比其他词句高;这就给信息的存储和传输带来了一定的便利:只要想办法把这些重复的字词用一些小于其本身长度的符号代替,就可以节省大量的空间。

拿郭老师的语录举个例子:

喝一口牛奶啊,喝一口牛奶,缪可啊缪可。

为了减小需要传输的数据量,可以编造一个字典

符号含义
00喝一口牛奶
10缪可
11
010
011

那么上一段话就能变成下面这个样子:

001101000101110011

为了进一步减小数据量,可以将每八位压成一个字节,这样这段话可以压缩在3个字节之内。而如果传送上边那句话的话,则需要38字节,因为一个汉字占用两个字节。

但是这种方法问题也很明显,在不同领域,或者不同的材料中,符号的出现概率肯定是不相同的,那么必然无法找出一个通用的词典。于是就产生了自适应词典,即词典就是原来已经编码序列的一部分。

比如我先读取了:

喝一口牛奶啊

那么我就可以拿这一部分做一个字典,以后如果出现相同的语句就可以直接代替了。具体怎么操作见下一部分。

算法原理

编码

LZ77方法定义了两块区域,查找缓冲区先行缓冲区

查找缓冲区包含了已经编码完成的一部分,先行缓冲区则包含了还未编码的一部分。线性缓冲区在查找缓冲区前面,并紧挨着查找缓冲区。

我们假设查找缓冲区和先行缓冲区长度均为6.

类似这个样子:
在这里插入图片描述
之后,就在查找缓冲区里找到所有与先行缓冲区第一个字符(设其下标为x)一样的字符。
每找到这样的一个字符(设其下标为y),就查看它后边的字符(下标为y+1的字符)是不是和缓冲区的下一个字符(下标为x+1)相同.
如果相同再看(y+2)位置的字符是不是与(x+2)位置的字符相同,不断继续下去,直到x+(n+1)与y+(n+1)不相同,此时长度n叫做匹配长度。

最终,总能找到一个匹配长度最长的y。然后就返回一个三元组(pos,length,nextSym)。其中:

  • pos为x-y,即选择的位置与待匹配位置的距离
  • length为匹配长度n
  • nextSym为失配符号,即位置x+n+1的符号,之所以返回这个符号,是为了处理找不到匹配的字符的情况,这种情况下可以返回(0,0,nextSym)。

在查找的过程中可能会发生以下情况:

  • 没有找到
  • 找到了
    • 正常匹配
    • 匹配长度超过查找缓冲区

下面将一一举例。

实例

在这里插入图片描述
在当前的状态,查找缓冲区中没有与’d’相同的符号,于是返回(0,0,d)。查找缓冲区和先行缓冲区同时向后移动1位。
在这里插入图片描述
在距离先行缓冲区第一个位置4,和距离先行缓冲区第一个位置6的地方可以找到与’b’相同的符号;他们的匹配长度分别为2和1,于是返回(4,2,e),并且查找缓冲区和先行缓冲区同时向后移动3位。
在这里插入图片描述
距离先行缓冲区为3的地方出现了’b’,而发现匹配长度可以一直延申到先行缓冲区。这种情况是允许的,即偏移量限制在查找缓冲区范围内,而匹配边界的范围在查找缓冲区和先行缓冲区范围内
此时,返回(3,5,d),查找缓冲区和先行缓冲区同时向后移动6位.
在这里插入图片描述
不断重复上述过程直到编码完成。

解码

解码其实很简单,就按照编码的规则反过来进行就好了。

以上个例子编出来的码进行解码示例:
假设前部分已经解码完成,此时字符为:
在这里插入图片描述
根据(0,0,d),只要在最后追加d即可:
在这里插入图片描述
之后根据(4,2,e),找出偏移为4的位置,复制两个字(实际过程中这两个字符是一个一个进行复制的)符并追加e
在这里插入图片描述
之后(3,5,d),这里将过程分解一下,一步一步来:
在这里插入图片描述
重复该过程,完成解码。

代码实现

猕hotel,猕猴桃啊,迷糊桃,这个东西要怎么吃呢,这个东西剥开,特别甜啊,我就先剥开了,我暂停一下。现在剥完了,我尝一下啊,尝一下好吃吗,尝一下甜不甜,尝一下啊。啊,好,没吃呢,尝一下甜不甜啊,啊!!啊!!!

继续用郭老师语录来做个示例:

编码

这里假定数据大小大于查找缓冲区和先行缓冲区大小之和。并且为了简便,最开始的查找缓冲区长度的字符并没有进行匹配。

str = "猕hotel,猕猴桃啊,猕糊桃,这个东西要怎么吃呢,这个东西剥开,特别甜啊,我就先剥开了,我暂停一下。现在剥完了,我尝一下啊,尝一下好吃吗,尝一下甜不甜,尝一下啊。啊,好,没吃呢,尝一下甜不甜啊,啊!!啊!!!"
buffer = ""
search_buf_length, look_ahead_buf_length, str_length = 25, 10, len(str)
search_buf_pos, look_ahead_buf_pos = 0, search_buf_length
encode_list = []

def Init():
    global buffer
    buffer = str[search_buf_pos:search_buf_pos+search_buf_length]
    for i in buffer:
        encode_list.append([0,0,i])
    buffer += str[look_ahead_buf_pos:look_ahead_buf_pos+look_ahead_buf_length]

def MoveForward(step):
    global search_buf_pos, look_ahead_buf_pos, buffer
    search_buf_pos += step; look_ahead_buf_pos += step
    buffer = str[search_buf_pos:search_buf_pos+search_buf_length+look_ahead_buf_length]

def Encode():
    sym_offset = search_buf_length
    max_length, max_offset, next_sym = 0, 0, buffer[sym_offset]
    buffer_length = len(buffer)
    if buffer_length - sym_offset == 1:
        encode_list.append([0,0,next_sym])
        return max_length
    for offset in range(1,search_buf_length+1):
        pos = sym_offset - offset
        n = 0
        while buffer[pos + n] == buffer[sym_offset + n]:
            n += 1
            if n == buffer_length - search_buf_length - 1: break
        if max_length < n:
            max_length = n
            max_offset = offset
            next_sym = buffer[sym_offset+n]
    encode_list.append([max_offset, max_length, next_sym])
    return max_length

def LZ77():
    while 1:
        step = Encode() + 1
        MoveForward(step)
        if look_ahead_buf_pos >= str_length: break

Init()
LZ77()
for i in encode_list:
    print(i)

def Decode(encode_lise):
    ans = ''
    for i in encode_list:
        offset, length, sym = i
        for j in range(length):
            ans += ans[-offset]
        ans += sym
    return ans

print(Decode(encode_list))

结果:

[0, 0, '猕']
[0, 0, 'h']
[0, 0, 'o']
[0, 0, 't']
[0, 0, 'e']
[0, 0, 'l']
[0, 0, ',']
[0, 0, '猕']
[0, 0, '猴']
[0, 0, '桃']
[0, 0, '啊']
[0, 0, ',']
[0, 0, '猕']
[0, 0, '糊']
[0, 0, '桃']
[0, 0, ',']
[0, 0, '这']
[0, 0, '个']
[0, 0, '东']
[0, 0, '西']
[0, 0, '要']
[0, 0, '怎']
[0, 0, '么']
[0, 0, '吃']
[0, 0, '呢']
[10, 5, '剥']
[0, 0, '开']
[7, 1, '特']
[0, 0, '别']
[0, 0, '甜']
[0, 0, '啊']
[5, 1, '我']
[0, 0, '就']
[0, 0, '先']
[11, 2, '了']
[7, 2, '暂']
[0, 0, '停']
[0, 0, '一']
[0, 0, '下']
[0, 0, '。']
[0, 0, '现']
[0, 0, '在']
[12, 1, '完']
[12, 3, '尝']
[11, 2, '啊']
[6, 1, '尝']
[5, 2, '好']
[0, 0, '吃']
[0, 0, '吗']
[7, 4, '甜']
[0, 0, '不']
[2, 1, ',']
[19, 4, '。']
[21, 2, '好']
[2, 1, '没']
[20, 1, '呢']
[20, 7, '啊']
[8, 1, '啊']
[0, 0, '!']
[1, 1, '啊']
[3, 2, '!']

看上去数据量并没有减少多少,因为这只是个演示(举例并不好),实际上前边的元组也会用特殊的编码方式,如霍夫曼编码等进行二次压缩。

解码

解码就很简单了:

def Decode(encode_lise):
    ans = ''
    for i in encode_list:
        offset, length, sym = i
        for j in range(length):
            ans += ans[-offset]
        ans += sym
    return ans

print(Decode(encode_list))
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值