首頁 >後端開發 >Python教學 >圖文詳解LZ77壓縮演算法編碼Python實作原理

圖文詳解LZ77壓縮演算法編碼Python實作原理

高洛峰
高洛峰原創
2017-03-23 16:05:214237瀏覽

前言

LZ77演算法是無損壓縮演算法,由以色列人Abraham Lempel發表於1977年。 LZ77是典型的基於字典的壓縮演算法,現在很多壓縮技術都是基於LZ77。鑑於其在資料壓縮領域的地位,本文將結合圖片和原始碼詳細介紹其原理。

原理介紹

首先介紹幾個專業術語。

1.lookahead buffer(不知道怎麼用中文表述,暫時稱為待編碼區):

等待編碼的區域

2. search buffer:

已經編碼的區域,搜尋緩衝區

3.滑動視窗:

指定大小的窗,包含「搜尋緩衝區」(左) + 「待編碼區」(右)

接下來,介紹具體的編碼過程:

為了編碼待編碼區,編碼器在滑動視窗的搜尋緩衝區尋找直到找到符合的字串# 。匹配字串的開始字串與待編碼緩衝區的距離稱為“偏移值”,匹配字串的長度稱為“匹配長度”。編碼器在編碼時,會一直在搜尋區中搜索,直到找到最大匹配字串,並輸出(o, l ),其中o是偏移值, l是匹配長度。然後視窗滑動l,繼續開始編碼。如果沒有找到匹配字串,則輸出(0, 0, c),c為待編碼區下一個等待編碼的字符,視窗滑動「1」。演算法實作將類似下面的:

while( lookAheadBuffer not empty )
 {
 get a pointer (position, match) to the longest match
 in the window for the lookAheadBuffer;
output a (position, length, char());
 shift the window length+1 characters along;
 }

主要步驟為:

1.設定編碼位置為輸入流的開始

2.在滑窗的待編碼區查找搜尋區中的最大匹配字串

3.如果找到字串,輸出(偏移值, 匹配長度), 視窗向前滑動「匹配長度」

4. 如果沒有找到,輸出(0, 0, 待編碼區的第一個字元),視窗向前滑動一個單位

5.如果待編碼區不為空,回到步驟2

描述實在是太複雜,還是結合實例來講解吧

實例:

現在有字串“AABCBBABC”,現在對其進行編碼。

一開始,視窗滑入如圖位置

圖文詳解LZ77壓縮演算法編碼Python實作原理

由圖可見,待編碼緩衝區有「AAB」三個字符,此時搜尋緩衝區還是空的。所以編碼第一個字符,由於搜尋區為空,故找不到匹配串,輸出(0,0, A),視窗右移一個單位,如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

此時待編碼區有「ABC」。開始編碼。最先編碼”A”,在搜尋區找到”A”。由於沒有超過待編碼區,故開始編碼”AB”,但在搜尋區沒有找到匹配字串,故無法編碼。因此只能編碼”A」。

輸出(1, 1)。即相對於待編碼區,偏移一個單位,匹配長度為1。視窗右滑動匹配長度,即移動1個單位。如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

一樣,沒找到,輸出(0, 0, B),右移1個單號,如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

輸出(0, 0, C),右移1個單位,如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

#輸出(2, 1),右移1個單位,如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

輸出(3, 1),右移1個單位,如下圖

圖文詳解LZ77壓縮演算法編碼Python實作原理

开始编码”A”,在搜索缓冲区查找到匹配字符串。由于待编码缓冲区没有超过,继续编码。开始编码”AB”,也搜索到。不要停止,继续编码“ABC”,找到匹配字符串。由于继续编码,则超过了窗口,故只编码“ABC”,输出(5, 3),偏移5,长度3。右移3个单位,如下图

圖文詳解LZ77壓縮演算法編碼Python實作原理

此时待编码缓冲区为空,停止编码。

最终输出结果如下

圖文詳解LZ77壓縮演算法編碼Python實作原理

python代码实现:

class Lz77:
    def init(self, inputStr):
        self.inputStr = inputStr #输入流
        self.searchSize = 5    #搜索缓冲区(已编码区)大小
        self.aheadSize = 3     #lookAhead缓冲区(待编码区)大小 
        self.windSpiltIndex = 0 #lookHead缓冲区开始的索引
        self.move = 0
        self.notFind = -1   #没有找到匹配字符串

    #得到滑动窗口的末端索引
    def getWinEndIndex(self):
        return self.windSpiltIndex + self.aheadSize

    #得到滑动窗口的始端索引
    def getWinStartIndex(self):
        return self.windSpiltIndex - self.searchSize

    #判断lookHead缓冲区是否为空
    def isLookHeadEmpty(self):
        return True if self.windSpiltIndex + self.move> len(self.inputStr) - 1   else False

    def encoding(self):
        step = 0
        print("Step   Position   Match   Output")
        while not self.isLookHeadEmpty():
            #1.滑动窗口
            self.winMove()
            #2. 得到最大匹配串的偏移值和长度
            (offset, matchLen) = self.findMaxMatch()
            #3.设置窗口下一步需要滑动的距离
            self.setMoveSteps(matchLen) 
            if matchLen == 0:
                #匹配为0,说明无字符串匹配,输出下一个需要编码的字母
                nextChar = self.inputStr[self.windSpiltIndex]
                result = (step, self.windSpiltIndex, '-',  '(0,0)' + nextChar)
            else:
                result = (step, self.windSpiltIndex, self.inputStr[self.windSpiltIndex - offset: self.windSpiltIndex - offset + matchLen], '(' + str(offset) + ',' + str(matchLen) + ')')
            #4.输出结果
            self.output(result)    
            step = step + 1        #仅用来设置第几步

    #滑动窗口(移动分界点)
    def winMove(self):
        self.windSpiltIndex = self.windSpiltIndex + self.move

    #寻找最大匹配字符并返回相对于窗口分界点的偏移值和匹配长度
    def findMaxMatch(self):
        matchLen = 0
        offset = 0
        minEdge = self.minEdge() + 1  #得到编码区域的右边界
        #遍历待编码区,寻找最大匹配串
        for i in range(self.windSpiltIndex + 1, minEdge):
            #print("i: %d" %i)
            offsetTemp = self.searchBufferOffest(i)
            if offsetTemp == self.notFind: 
                return (offset, matchLen)
            offset = offsetTemp #偏移值

            matchLen = matchLen + 1  #每找到一个匹配串,加1

        return (offset, matchLen)

    #入参字符串是否存在于搜索缓冲区,如果存在,返回匹配字符串的起始索引
    def searchBufferOffest(self, i):
        searchStart = self.getWinStartIndex()
        searchEnd = self.windSpiltIndex 
        #下面几个if是处理开始时的特殊情况
        if searchEnd < 1:
            return self.notFind
        if searchStart < 0:
            searchStart = 0
            if searchEnd == 0:
                searchEnd = 1
        searchStr = self.inputStr[searchStart : searchEnd]  #搜索区字符串
        findIndex = searchStr.find(self.inputStr[self.windSpiltIndex : i])
        if findIndex == -1:
            return -1
        return len(searchStr) - findIndex

    #设置下一次窗口需要滑动的步数
    def setMoveSteps(self, matchLen):
        if matchLen == 0:
            self.move = 1
        else:
            self.move = matchLen

    def minEdge(self):
        return len(self.inputStr)  if len(self.inputStr) - 1 < self.getWinEndIndex() else self.getWinEndIndex() + 1

    def output(self, touple):
        print("%d      %d           %s     %s" % touple)

if name == "main":
    lz77 = Lz77("AABCBBABC")
    lz77.encoding()

只是简单的写了下,没有过多考虑细节,请注意,这不是最终的代码,只是用来阐述原理,仅供参考。输出结果就是上面的输出(格式由于坑爹的博客园固定样式,代码位置有偏移,请注意




以上是圖文詳解LZ77壓縮演算法編碼Python實作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn