LZ77壓縮演算法編碼Python實現原理圖解

轉瞬之夏的部落格發表於2014-12-02

前言

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”,現在對其進行編碼。

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

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

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

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

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

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

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

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

開始編碼”A”,在搜尋緩衝區查詢到匹配字串。由於待編碼緩衝區沒有超過,繼續編碼。開始編碼”AB”,也搜尋到。不要停止,繼續編碼“ABC”,找到匹配字串。由於繼續編碼,則超過了視窗,故只編碼“ABC”,輸出(5, 3),偏移5,長度3。右移3個單位,如下圖

此時待編碼緩衝區為空,停止編碼。

最終輸出結果如下

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()

只是簡單的寫了下,沒有過多考慮細節,請注意,這不是最終的程式碼,只是用來闡述原理,僅供參考。輸出結果就是上面的輸出(格式由於坑爹的部落格園固定樣式,程式碼位置有偏移,請注意)

相關文章