LZ77 壓縮和解壓縮

前言

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

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

解壓縮:

Decompression Process Example

The input stream for this example is the output of the compression example above.

The following table shows the construction of the output stream as it is built from the sequence of pointers in the input stream. The table includes the following columns:

Step: Indicates the number of the decoding step. A step in the table finishes every time the decoding algorithm appends the set of bytes identified by the pointer to the output stream.

Input Pointer: The next pointer from the input stream.

Append Bytes: The bytes that the pointer identifies to be appended to the output stream.

Output Stream: The output stream as it looks at the end of each step.

Step

Input Pointer

Append Bytes

Output Stream

1.

(0,0)A

A

A

2.

(1,1)

A

A A

3.

(0,0)B

B

A A B

4.

(0,0)C

C

A A B C

5.

(2,1)

B

A A B C B

6.

(1,1)

B

A A B C B B

7.

(5,3)

ABC

A A B C B B A B C

 

參考文章:

  http://msdn.microsoft.com/en-us/library/ee916854.aspx

  http://en.wikipedia.org/wiki/LZ77_and_LZ78

  http://cs.stanford.edu/people/eroberts/courses/soco/projects/2000-01/data-compression/lossless/lz77/algorithm.htm

  以上幾篇文章都是很好的講解LZ77原理的,大家有興趣的可以參考下。由於國內介紹該算法的比較少,故這些英文文章幫助還是挺大的。

發佈了47 篇原創文章 · 獲贊 12 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章