1. 程式人生 > >【資料壓縮】LZ77演算法原理及實現

【資料壓縮】LZ77演算法原理及實現

1. 引言

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. 實現

# 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. 引言 LZ77演算法是採用字典做資料壓縮的演算法,由以色列的兩位大神Jacob Ziv與Abraham Lempel在1977年發表的論文《A Universal Algorithm for Sequential Data Compression》中提出。 基於統計的資料壓縮編碼,比如Huffman編

資料壓縮LZ78演算法原理實現

在提出基於滑動視窗的LZ77演算法後,兩位大神Jacob Ziv與Abraham Lempel於1978年在發表的論文 [1]中提出了LZ78演算法;與LZ77演算法不同的是LZ78演算法使用動態樹狀詞典維護歷史字串。 1. 原理 壓縮 LZ78演算法的壓縮過程非常簡單。在壓縮時維護一個動態詞典Dictio

機器學習Apriori演算法——原理程式碼實現(Python版)

Apriopri演算法 Apriori演算法在資料探勘中應用較為廣泛,常用來挖掘屬性與結果之間的相關程度。對於這種尋找資料內部關聯關係的做法,我們稱之為:關聯分析或者關聯規則學習。而Apriori演算法就是其中非常著名的演算法之一。關聯分析,主要是通過演算法在大規模資料集中尋找頻繁項集和關聯規則。

常用晶片ULN2003工作原理中文資料(例項:STM32驅動28BYJ48步進電機)

ULN2003的基本介紹ULN2003的概述ULN2003是高耐壓、大電流複合電晶體陣列,由七個矽NPN 複合電晶體組成。一般採用DIP—16 或SOP—16 塑料封裝。ULN2003的主要特點:ULN2003 的每一對達林頓都串聯一個2.7K 的基極電阻,在5V 的工作電壓

資料壓縮的歷史、原理常用演算法

壓縮,是為了減少儲存空間而把資料轉換成比原始格式更緊湊形式的過程。資料壓縮的概念相當古老,可以追溯到發明了摩爾斯碼的19世紀中期。 摩爾斯碼的發明,是為了使電報員能夠通過電報系統,利用一系列可聽到的脈衝訊號傳遞字母資訊,從而實現文字訊息的傳輸。摩爾斯碼的發明者

資料結構和演算法 | 氣泡排序演算法原理實現和優化

氣泡排序(Bubble Sort)是排序演算法裡面比較簡單的一個排序。它重複地走訪要排序的數列,一次比較兩個資料元素,如果順序不對則進行交換,並一直重複這樣的走訪操作,直到沒有要交換的資料元素為止。 氣泡排序的原理 為了更深入地理解氣泡排序的操作步驟,我們現在

資料結構和演算法 | 簡單選擇排序演算法原理實現

選擇排序是一種非常簡單的排序演算法,就是在序列中依次選擇最大(或者最小)的數,並將其放到待排序的數列的起始位置。 簡單選擇排序的原理 簡單選擇排序的原理非常簡單,即在待排序的數列中尋找最大(或者最小)的一個數,與第 1 個元素進行交換,接著在剩餘的待排序的數列

資料結構和演算法 | 插入排序演算法原理實現和優化

插入排序演算法是所有排序方法中最簡單的一種演算法,其主要的實現思想是將資料按照一定的順序一個一個的插入到有序的表中,最終得到的序列就是已經排序好的資料。 直接插入排序是插入排序演算法中的一種,採用的方法是:在新增新的記錄時,使用順序查詢的方式找到其要插入的位置,

資料結構和演算法 | 歸併排序演算法原理實現和優化

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。 歸併排序的原理 歸

網際網路安全DDoS攻防原理實戰

分散式拒絕服務(DDoS:Distributed Denial of Service)攻擊指藉助於客戶/伺服器技術,將多個計算機聯合起來作為攻擊平臺,對一個或多個目標發動DDoS攻擊,從而成倍地提高拒絕服務攻擊的威力。通常,攻擊者使用一個偷竊帳號將DDoS主控程式安裝在一個計算機上,在一個設定的時間

特徵匹配RANSAC演算法原理與原始碼解析

轉載請註明出處:http://blog.csdn.net/luoshixian099/article/details/50217655 勿在浮沙築高臺   隨機抽樣一致性(RANSAC)演算法,可以在一組包含“外點”的資料集中,採用不斷迭代的方法,尋找最優引數模型,不符合最

資料壓縮Huffman編碼

1. 壓縮編碼概述 資料壓縮在日常生活極為常見,平常所用到jpg、mp3均採用資料壓縮(採用Huffman編碼)以減少佔用空間。編碼\(C\)是指從字元空間\(A\)到碼字表\(X\)的對映。資料壓縮編碼指編碼後資訊的長度較於原始資訊要短。本文試圖探討Huffman編碼是如何保證唯一可譯性、如何壓縮、以及壓縮

資料結構雜湊表雜湊桶的基本操作

  順序搜尋和二叉搜尋樹中,元素儲存位置和元素各關鍵碼之間沒有對應的關係,這就導致在查詢一個元素時,必須經過關鍵碼的多次比較。那麼是否有這樣一種資料結構,可以不經過任何比較,直接找到想要搜尋的元素呢?答案是肯定的,那就是通過某種函式(hashFunc)使得元素的儲存位置與它的

資料結構雙向連結串列的實現

文章目錄 LinkList.h LinkLish.c LinkList.h #ifndef __LINKLIST_H__ #define __LINKLIST_H__ #include <stdio.h>

目標檢測目標檢測原理實現(五)--基於Cascade分類器的目標檢測

基於Cascade分類器的目標檢測        從今天開始進入基於機器學習的目標檢測,前幾節雖然也接觸了一些機器學習的方法,但它主要是做輔助工作,機器學習的方法和非機器學習的方法結合在一起使用,說到這想起來前幾天看到一位博士師兄發的笑話,說的是百度實驗室:  

資料結構鏈式棧的實現(C語言)

棧的鏈式儲存稱為鏈式棧,鏈式棧是一種特殊的單鏈表,它的插入和刪除規定在單鏈表的同一端進行。鏈式棧的棧頂指標一般用top表示。(個人理解:相當於只對單鏈表的第一個結點進行操作) 鏈式棧要掌握以下基本操作: 1、建立一個空鏈式棧 2、判斷鏈式棧是否為空 3、讀鏈式棧的

資料正規化 (data normalization) 的原理實現 (Python sklearn)

原理 資料正規化(data normalization)是將資料的每個樣本(向量)變換為單位範數的向量,各樣本之間是相互獨立的.其實際上,是對向量中的每個分量值除以正規化因子.常用的正規化因子有 L1, L2 和 Max.假設,對長度為 n 的向量,其正規化因子 z 的計算公式,如下所示:

排序演算法 | 希爾排序演算法原理實現和優化

希爾排序也是一種插入排序演算法,也叫作縮小增量排序,是直接插入排序的一種更高效的改進演算法。 希爾排序因其設計者希爾(Donald Shell)的名字而得名,該演算法在 1959 年被公佈。一些老版本的教科書和參考手冊把該演算法命名為 Shell-Metzner

氣泡排序演算法原理實現(超詳細)

氣泡排序(Bubble Sort)是排序演算法裡面比較簡單的一個排序。它重複地走訪要排序的數列,一次比較兩個資料元素,如果順序不對則進行交換,並一直重複這樣的走訪操作,直到沒有要交換的資料元素為止。 氣泡排序的原理 為了更深入地理解氣泡排序的操作步驟,我們現在看一下氣泡排序的原理。 首先我們肯定有一個數組

K-means聚類的演算法原理實現

原地址 1、如何理解K-Means演算法? 2、如何尋找K值及初始質心? 3、如何應用K-Means演算法處理資料?K-means聚類的演算法原理 K-Means是聚類演算法中的一種,其中K表示類別數,Means表示均值。顧名思義K-Means是一種通過均值對資料點進行聚類的演算法。K-Me