再說LZ77壓縮算法

LZ77 算法的基本流程。

一、從當前壓縮位置開始,考察未編碼的數據,並試圖在滑動窗口中找出最長的匹配字符串,若是找到,則進行步驟 2,不然進行步驟 3。算法

二、輸出三元符號組 ( off, len, c )。其中 off 爲窗口中匹配字符串相對窗口邊界的偏移,len 爲可匹配的長度,c 爲下一個字符。而後將窗口向後滑動 len + 1 個字符,繼續步驟 1。數組

三、輸出三元符號組 ( 0, 0, c )。其中 c 爲下一個字符。而後將窗口向後滑動 len + 1 個字符,繼續步驟 1。優化

咱們結合實例來講明。假設窗口的大小爲 10 個字符,咱們剛編碼過的 10 個字符是:abcdbbccaa,即將編碼的字符爲:abaeaaabaee編碼

咱們首先發現,能夠和要編碼字符匹配的最長串爲 ab ( off = 0, len = 2 ), ab 的下一個字符爲 a,咱們輸出三元組:( 0, 2, a )設計

如今窗口向後滑動 3 個字符,窗口中的內容爲:dbbccaaaba指針

下一個字符 e 在窗口中沒有匹配,咱們輸出三元組:( 0, 0, e )blog

窗口向後滑動 1 個字符,其中內容變爲:bbccaaabae排序

咱們立刻發現,要編碼的 aaabae 在窗口中存在( off = 4, len = 6 ),其後的字符爲 e,咱們能夠輸出:( 4, 6, e )圖片

這樣,咱們將能夠匹配的字符串都變成了指向窗口內的指針,並由此完成了對上述數據的壓縮。ip

解壓縮的過程十分簡單,只要咱們向壓縮時那樣維護好滑動的窗口,隨着三元組的不斷輸入,咱們在窗口中找到相應的匹配串,綴上後繼字符 c 輸出(若是 off 和 len 都爲 0 則只輸出後繼字符 c )便可還原出原始數據。

固然,真正實現 LZ77 算法時還有許多複雜的問題須要解決,下面咱們就來對可能碰到的問題逐一加以探討。

編碼方法

咱們必須精心設計三元組中每一個份量的表示方法,才能達到較好的壓縮效果。通常來說,編碼的設計要根據待編碼的數值的分佈狀況而定。對於三元組的第一個份量——窗口內的偏移,一般的經驗是,偏移接近窗口尾部的狀況要多於接近窗口頭部的狀況,這是由於字符串在與其接近的位置較容易找到匹配串,但對於普通的窗口大小(例如 4096 字節)來講,偏移值基本仍是均勻分佈的,咱們徹底能夠用固定的位數來表示它。

編碼 off 須要的位數 bitnum = upper_bound( log2( MAX_WND_SIZE ))

由此,若是窗口大小爲 4096,用 12 位就能夠對偏移編碼。若是窗口大小爲 2048,用 11 位就能夠了。複雜一點的程序考慮到在壓縮開始時,窗口大小並無達到 MAX_WND_SIZE,而是隨着壓縮的進行增加,所以能夠根據窗口的當前大小動態計算所須要的位數,這樣能夠略微節省一點空間。

對於第二個份量——字符串長度,咱們必須考慮到,它在大多數時候不會太大,少數狀況下才會發生大字符串的匹配。顯然可使用一種變長的編碼方式來表示該長度值。在前面咱們已經知道,要輸出變長的編碼,該編碼必須知足前綴編碼的條件。其實 Huffman 編碼也能夠在此處使用,但卻不是最好的選擇。適用於此處的好的編碼方案不少,我在這裏介紹其中兩種應用很是普遍的編碼。

第一種叫 Golomb 編碼。假設對正整數 x 進行 Golomb 編碼,選擇參數 m,令

b = 2m

q = INT((x - 1)/b)

r = x - qb - 1

則 x 能夠被編碼爲兩部分,第一部分是由 q 個 1 加 1 個 0 組成,第二部分爲 m 位二進制數,其值爲 r。咱們將 m = 0, 1, 2, 3 時的 Golomb 編碼表列出:

     值 x          m = 0         m = 1         m = 2         m = 3

-------------------------------------------------------------

      1               0           0 0          0 00          0 000

      2              10           0 1          0 01          0 001

      3             110          10 0          0 10          0 010

      4            1110          10 1          0 11          0 011

      5           11110         110 0         10 00          0 100

      6          111110         110 1         10 01          0 101

      7         1111110        1110 0         10 10          0 110

      8        11111110        1110 1         10 11          0 111

      9       111111110       11110 0        110 00         10 000

從表中咱們能夠看出,Golomb 編碼不但符合前綴編碼的規律,並且能夠用較少的位表示較小的 x 值,而用較長的位表示較大的 x 值。這樣,若是 x 的取值傾向於比較小的數值時,Golomb 編碼就能夠有效地節省空間。固然,根據 x 的分佈規律不一樣,咱們能夠選取不一樣的 m 值以達到最好的壓縮效果。

對咱們上面討論的三元組 len 值,咱們能夠採用 Golomb 方式編碼。上面的討論中 len 可能取 0,咱們只需用 len + 1 的 Golomb 編碼便可。至於參數 m 的選擇,通常經驗是取 3 或 4 便可。

能夠考慮的另外一種變長前綴編碼叫作 γ 編碼。它也分做先後兩個部分,假設對 x 編碼,令 q = int( log2x ),則編碼的前一部分是 q 個 1 加一個 0,後一部分是 q 位長的二進制數,其值等於 x - 2q 。γ編碼表以下:

     值 x      γ編碼

---------------------

      1         0

      2        10 0

      3        10 1

      4       110 00

      5       110 01

      6       110 10

      7       110 11

      8      1110 000

      9      1110 001

其實,若是對 off 值考慮其傾向於窗口後部的規律,咱們也能夠採用變長的編碼方法。但這種方式對窗口較小的狀況改善並不明顯,有時壓縮效果還不如固定長編碼。

對三元組的最後一個份量——字符 c,由於其分佈並沒有規律可循,咱們只能老老實實地用 8 個二進制位對其編碼。

根據上面的敘述,相信你必定也能寫出高效的編碼和解碼程序了。

另外一種輸出方式

LZ77 的原始算法採用三元組輸出每個匹配串及其後續字符,即便沒有匹配,咱們仍然須要輸出一個 len = 0 的三元組來表示單個字符。試驗代表,這種方式對於某些特殊狀況(例如同一字符不斷重複的情形)有着較好的適應能力。但對於通常數據,咱們還能夠設計出另一種更爲有效的輸出方式:將匹配串和不能匹配的單個字符分別編碼、分別輸出,輸出匹配串時不一樣時輸出後續字符。

咱們將每個輸出分紅匹配串和單個字符兩種類型,並首先輸出一個二進制位對其加以區分。例如,輸出 0 表示下面是一個匹配串,輸出 1 表示下面是一個單個字符。

以後,若是要輸出的是單個字符,咱們直接輸出該字符的字節值,這要用 8 個二進制位。也就是說,咱們輸出一個單個的字符共須要 9 個二進制位。

若是要輸出的是匹配串,咱們按照前面的方法依次輸出 off 和 len。對 off,咱們能夠輸出定長編碼,也能夠輸出變長前綴碼,對 len 咱們輸出變長前綴碼。有時候咱們能夠對匹配長度加以限制,例如,咱們能夠限制最少匹配 3 個字符。由於,對於 2 個字符的匹配串,咱們使用匹配串的方式輸出並不必定比咱們直接輸出 2 個單個字符(須要 18 位)節省空間(是否節省取決於咱們採用何種編碼輸出 off 和 len)。

這種輸出方式的優勢是輸出單個字符的時候比較節省空間。另外,由於不強求每次都外帶一個後續字符,能夠適應一些較長匹配的狀況。

1. 原理部分:

  有兩種形式的重複存在於計算機數據中,zip 就是對這兩種重複進行了壓縮。

  一種是短語形式的重複,即三個字節以上的重複,對於這種重複,zip用兩個數字:1.重複位置距當前壓縮位置的距離;2.重複的長度,來表示這個重複,假設這兩個數字各佔一個字節,因而數據便獲得了壓縮,這很容易理解。

  一個字節有 0 - 255 共 256 種可能的取值,三個字節有 256 * 256 * 256 共一千六百多萬種可能的狀況,更長的短語取值的可能狀況以指數方式增加,出現重複的機率彷佛極低,實則否則,各類類型的數據都有出現重複的傾向,一篇論文中,爲數很少的術語傾向於重複出現;一篇小說,人名和地名會重複出現;一張上下漸變的背景圖片,水平方向上的像素會重複出現;程序的源文件中,語法關鍵字會重複出現(咱們寫程序時,多少次先後copy、paste?),以幾十 K 爲單位的非壓縮格式的數據中,傾向於大量出現短語式的重複。通過上面提到的方式進行壓縮後,短語式重複的傾向被徹底破壞,因此在壓縮的結果上進行第二次短語式壓縮通常是沒有效果的。

  第二種重複爲單字節的重複,一個字節只有256種可能的取值,因此這種重複是必然的。其中,某些字節出現次數可能較多,另外一些則較少,在統計上有分佈不均勻的傾向,這是容易理解的,好比一個 ASCII 文本文件中,某些符號可能不多用到,而字母和數字則使用較多,各字母的使用頻率也是不同的,聽說字母 e 的使用機率最高;許多圖片呈現深色調或淺色調,深色(或淺色)的像素使用較多(這裏順便提一下:png 圖片格式是一種無損壓縮,其核心算法就是 zip 算法,它和 zip 格式的文件的主要區別在於:做爲一種圖片格式,它在文件頭處存放了圖片的大小、使用的顏色數等信息);上面提到的短語式壓縮的結果也有這種傾向:重複傾向於出如今離當前壓縮位置較近的地方,重複長度傾向於比較短(20字節之內)。這樣,就有了壓縮的可能:給 256 種字節取值從新編碼,使出現較多的字節使用較短的編碼,出現較少的字節使用較長的編碼,這樣一來,變短的字節相對於變長的字節更多,文件的總長度就會減小,而且,字節使用比例越不均勻,壓縮比例就越大。

  在進一步討論編碼的要求以及辦法前,先提一下:編碼式壓縮必須在短語式壓縮以後進行,由於編碼式壓縮後,原先八位二進制值的字節就被破壞了,這樣文件中短語式重複的傾向也會被破壞(除非先進行解碼)。另外,短語式壓縮後的結果:那些剩下的未被匹配的單、雙字節和獲得匹配的距離、長度值仍然具備取值分佈不均勻性,所以,兩種壓縮方式的順序不能變。

  在編碼式壓縮後,以連續的八位做爲一個字節,原先未壓縮文件中所具備的字節取值不均勻的傾向被完全破壞,成爲隨機性取值,根據統計學知識,隨機性取值具備均勻性的傾向(好比拋硬幣試驗,拋一千次,正反面朝上的次數都接近於 500 次)。所以,編碼式壓縮後的結果沒法再進行編碼式壓縮。

  短語式壓縮和編碼式壓縮是目前計算機科學界研究出的僅有的兩種無損壓縮方法,它們都沒法重複進行,因此,壓縮文件沒法再次壓縮(實際上,能反覆進行的壓縮算法是不可想象的,由於最終會壓縮到 0 字節)。

  短語式重複的傾向和字節取值分佈不均勻的傾向是能夠壓縮的基礎,兩種壓縮的順序不能互換的緣由也說了,下面咱們來看編碼式壓縮的要求及方法:

首先,爲了使用不定長的編碼表示單個字符,編碼必須符合「前綴編碼」的要求,即較短的編碼決不能是較長編碼的前綴,反過來講就是,任何一個字符的編碼,都不是由另外一個字符的編碼加上若干位 0 或 1 組成,不然解壓縮程序將沒法解碼。

看一下前綴編碼的一個最簡單的例子:

符號 編碼

A 0

B 10

C 110

D 1110

E 11110

有了上面的碼錶,你必定能夠輕鬆地從下面這串二進制流中分辨出真正的信息內容了:

1110010101110110111100010 - DABBDCEAAB

要構造符合這一要求的二進制編碼體系,二叉樹是最理想的選擇。考察下面這棵二叉樹:

        根(root)

       0  |   1

       +-------+--------+

    0  | 1   0  |  1

    +-----+------+ +----+----+

    |     | |     |

    a      | d     e

     0  |  1

     +-----+-----+

     |     |

     b     c

要編碼的字符老是出如今樹葉上,假定從根向樹葉行走的過程當中,左轉爲0,右轉爲1,則一個字符的編碼就是從根走到該字符所在樹葉的路徑。正由於字符只能出如今樹葉上,任何一個字符的路徑都不會是另外一字符路徑的前綴路徑,符合要求的前綴編碼也就構形成功了:

a - 00 b - 010 c - 011 d - 10 e - 11

接下來來看編碼式壓縮的過程:

爲了簡化問題,假定一個文件中只出現了 a,b,c,d ,e四種字符,它們的出現次數分別是

a : 6次

b : 15次

c : 2次

d : 9次

e : 1次

若是用定長的編碼方式爲這四種字符編碼: a : 000 b : 001 c : 010 d : 011 e : 100

那麼整個文件的長度是 3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99

用二叉樹表示這四種編碼(其中葉子節點上的數字是其使用次數,非葉子節點上的數字是其左右孩子使用次數之和):

          根

           |

      +---------33---------+

      |        |

   +----32---+     +----1---+

   |    |     |    |

+-21-+   +-11-+    +--1--+  

|   |   |   |    |   |

6  15  2  9    1   

(若是某個節點只有一個子節點,能夠去掉這個子節點。)

         根

         |

        +------33------+

       |     |

    +-----32----+     1

    |      |

  +--21--+  +--11--+

  |   |  |   |

  6   15 2    9

如今的編碼是: a : 000 b : 001 c : 010 d : 011 e : 1 仍然符合「前綴編碼」的要求。

第一步:若是發現下層節點的數字大於上層節點的數字,就交換它們的位置,並從新計算非葉子節點的值。

先交換11和1,因爲11個字節縮短了一位,1個字節增加了一位,總文件縮短了10位。

           根

            |

       +----------33---------+

       |        |

   +-----22----+     +----11----+

   |      |     |     |

+--21--+    1      2     9

|     |

6   15

再交換15和一、6和2,最終獲得這樣的樹:

           根

            |

       +----------33---------+

       |        |

     +-----18----+    +----15----+

    |      |    |     |

  +--3--+    15   6     9

  |   |

  2   1

這時全部上層節點的數值都大於下層節點的數值,彷佛沒法再進一步壓縮了。可是咱們把每一層的最小的兩個節點結合起來,常會發現仍有壓縮餘地。

第二步:把每一層的最小的兩個節點結合起來,從新計算相關節點的值。

在上面的樹中,第1、2、四三層都只有一或二個節點,沒法從新組合,但第三層上有四個節點,咱們把最小的3和6結合起來,並從新計算相關節點的值,成爲下面這棵樹。

           根

            |

       +----------33---------+

       |         |

    +------9-----+    +----24----+

    |      |    |     |

   +--3--+    6   15    9

   |   |

  2  1

而後,再重複作第一步。

這時第二層的9小於第三層的15,因而能夠互換,有9個字節增加了一位,15個字節縮短了一位,文件總長度又縮短了6位。而後從新計算相關節點的值。

           根

            |

       +----------33---------+

       |        |

       15     +----18----+ 

            |    |

         +------9-----+   9

         |      |

         +--3--+   6

         |   |

         2  1

這時發現全部的上層節點都大於下層節點,每一層上最小的兩個節點被並在了一塊兒,也不可能再產生比同層其餘節點更小的父節點了。

這時整個文件的長度是 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63

這時能夠看出編碼式壓縮的一個基本前提:各節點之間的值要相差比較懸殊,以使某兩個節點的和小於同層或下層的另外一個節點,這樣,交換節點纔有利益。

因此歸根結底,原始文件中的字節使用頻率必須相差較大,不然將沒有兩個節點的頻率之和小於同層或下層其餘節點的頻率,也就沒法壓縮。反之,相差得越懸殊,兩個節點的頻率之和比同層或下層節點的頻率小得越多,交換節點以後的利益也越大。

在這個例子中,通過上面兩步不斷重複,獲得了最優的二叉樹,但不能保證在全部狀況下,都能經過這兩步的重複獲得最優二叉樹,下面來看另外一個例子:

                         根

                         |

              +---------19--------+

              |                   |

      +------12------+            7

      |              |

  +---5---+      +---7---+

  |       |      |       |

+-2-+   +-3-+  +-3-+   +-4-+

|   |   |   |  |   |   |   |

1   1   1   2  1   2   2   2

這個例子中,全部上層節點都大於等於下層節點,每一層最小的兩個節點結合在了一塊兒,但仍然能夠進一步優化:

                         根

                         |

              +---------19--------+

              |                   |

      +------12------+            7

      |              |

  +---4---+      +---8---+

  |       |      |       |

+-2-+   +-2-+  +-4-+   +-4-+

|   |   |   |  |   |   |   |

1   1   1   1  2   2   2   2

經過最低一層的第4第5個節點對換,第3層的8大於第2層的7。

到這裏,咱們得出這樣一個結論:一棵最優二叉編碼樹(全部上層節點都沒法和下層節點交換),必須符合這樣兩個條件:

1.全部上層節點都大於等於下層節點。

2.某節點,設其較大的子節點爲m,較小的子節點爲n,m下的任一層的全部節點都應大於等於n下的該層的全部節點。

當符合這兩個條件時,任一層都沒法產生更小的節點去和下層節點交換,也沒法產生更大的節點去和上層節點交換。

上面的兩個例子是比較簡單的,實際的文件中,一個字節有256種可能的取值,因此二叉樹的葉子節點多達256個,須要不斷的調整樹形,最終的樹形可能很是複雜,有一種很是精巧的算法能夠快速地建起一棵最優二叉樹,這種算法由 D.Huffman(戴·霍夫曼)提出,下面咱們先來介紹霍夫曼算法的步驟,而後再來證實經過這麼簡單的步驟得出的樹形確實是一棵最優二叉樹。

霍夫曼算法的步驟是這樣的:

·從各個節點中找出最小的兩個節點,給它們建一個父節點,值爲這兩個節點之和。

·而後從節點序列中去除這兩個節點,加入它們的父節點到序列中。

重複上面兩個步驟,直到節點序列中只剩下惟一一個節點。這時一棵最優二叉樹就已經建成了,它的根就是剩下的這個節點。

仍以上面的例子來看霍夫曼樹的創建過程。

最初的節點序列是這樣的:

a(6)  b(15)  c(2)  d(9)  e(1)

把最小的c和e結合起來

                   | (3)

a(6)   b(15)   d(9)   +------+------+

              |      |

              c     e

不斷重複,最終獲得的樹是這樣的:

       根

        |

   +-----33-----+

   |     |

   15   +----18----+   

       |       |

       9  +------9-----+

          |       |

         6     +--3--+

              |   |

              2  1

這時各個字符的編碼長度和前面咱們說過的方法獲得的編碼長度是相同的,於是文件的總長度也是相同的: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63

考察霍夫曼樹的創建過程當中的每一步的節點序列的變化:

6  15 2 9 1

6  15 9 3

15 9  9

15 18

33

下面咱們用逆推法來證實對於各類不一樣的節點序列,用霍夫曼算法創建起來的樹老是一棵最優二叉樹:

對霍夫曼樹的創建過程運用逆推法:

當這個過程當中的節點序列只有兩個節點時(好比前例中的15和18),確定是一棵最優二叉樹,一個編碼爲0,另外一個編碼爲1,沒法再進一步優化。

而後往前步進,節點序列中不斷地減小一個節點,增長兩個節點,在步進過程當中將始終保持是一棵最優二叉樹,這是由於:

1.按照霍夫曼樹的創建過程,新增的兩個節點是當前節點序列中最小的兩個,其餘的任何兩個節點的父節點都大於(或等於)這兩個節點的父節點,只要前一步是最優二叉樹,其餘的任何兩個節點的父節點就必定都處在它們的父節點的上層或同層,因此這兩個節點必定處在當前二叉樹的最低一層。

2.這兩個新增的節點是最小的,因此沒法和其餘上層節點對換。符合咱們前面說的最優二叉樹的第一個條件。

3.只要前一步是最優二叉樹,因爲這兩個新增的節點是最小的,即便同層有其餘節點,也沒法和同層其餘節點從新結合,產生比它們的父節點更小的上層節點來和同層的其餘節點對換。它們的父節點小於其餘節點的父節點,它們又小於其餘全部節點,只要前一步符合最優二叉樹的第二個條件,到這一步仍將符合。

這樣一步步逆推下去,在這個過程當中霍夫曼樹每一步都始終保持着是一棵最優二叉樹。

因爲每一步都從節點序列中刪除兩個節點,新增一個節點,霍夫曼樹的創建過程共需 (原始節點數 - 1) 步,因此霍夫曼算法不失爲一種精巧的編碼式壓縮算法。

附:對於 huffman 樹,《計算機程序設計藝術》中有徹底不一樣的證實,大意是這樣的:

1.二叉編碼樹的內部節點(非葉子節點)數等於外部節點(葉子節點)數減1。

2.二叉編碼樹的外部節點的加權路徑長度(值乘以路徑長度)之和,等於全部內部節點值之和。(這兩條均可以經過對節點數運用數學概括法來證實,留給你們作練習。)

3.對 huffman 樹的創建過程運用逆推,當只有一個內部節點時,確定是一棵最優二叉樹。

4.往前步進,新增兩個最小的外部節點,它們結合在一塊兒產生一個新的內部節點,當且僅當原先的內部節點集合是極小化的,加入這個新的內部節點後還是極小化的。(由於最小的兩個節點結合在一塊兒,並處於最低層,相對於它們分別和其餘同層或上層節點結合在一塊兒,至少不會增長加權路徑長度。)

5.隨着內部節點數逐個增長,內部節點集合總維持極小化。

2.實現部分

  若是世界上從沒有一個壓縮程序,咱們看了前面的壓縮原理,將有信心必定能做出一個能夠壓縮大多數格式、內容的數據的程序,當咱們着手要作這樣一個程序的時候,會發現有不少的難題須要咱們去一個個解決,下面將逐個描述這些難題,並詳細分析 zip 算法是如何解決這些難題的,其中不少問題帶有廣泛意義,好比查找匹配,好比數組排序等等,這些都是說不盡的話題,讓咱們深刻其中,作一番思考。

咱們前面說過,對於短語式重複,咱們用「重複距當前位置的距離」和「重複的長度」這兩個數字來表示這一段重複,以實現壓縮,如今問題來了,一個字節能表示的數字大小爲 0 -255,然而重複出現的位置和重複的長度均可能超過 255,事實上,二進制數的位數肯定下來後,所能表示的數字大小的範圍是有限的,n位的二進制數能表示的最大值是2的n次方減1,若是位數取得太大,對於大量的短匹配,可能不但起不到壓縮做用,反而增大了最終的結果。針對這種狀況,有兩種不一樣的算法來解決這個問題,它們是兩種不一樣的思路。一種稱爲 lz77 算法,這是一種很天然的思路:限制這兩個數字的大小,以取得折衷的壓縮效果。例如距離取 15 位,長度取 8 位,這樣,距離的最大取值爲 32 k - 1,長度的最大取值爲 255,這兩個數字佔 23 位,比三個字節少一位,是符合壓縮的要求的。讓咱們在頭腦中想象一下 lz77 算法壓縮進行時的狀況,會出現有意思的模型:

   最遠匹配位置->          當前處理位置->

───┸─────────────────╂─────────────>壓縮進行方向

   已壓縮部分             ┃    未壓縮部分

  在最遠匹配位置和當前處理位置之間是能夠用來查找匹配的「字典」區域,隨着壓縮的進行,「字典」區域從待壓縮文件的頭部不斷地向後滑動,直到達到文件的尾部,短語式壓縮也就結束了。

  解壓縮也很是簡單:

         ┎────────拷貝────────┒

 匹配位置    ┃          當前處理位置  ┃

   ┃<──匹配長度──>┃       ┠─────∨────┨

───┸──────────┸───────╂──────────┸─>解壓進行方向

   已解壓部分              ┃    未解壓部分

  不斷地從壓縮文件中讀出匹配位置值和匹配長度值,把已解壓部分的匹配內容拷貝到解壓文件尾部,遇到壓縮文件中那些壓縮時未能獲得匹配,而是直接保存的單、雙字節,解壓時只要依次直接拷貝到文件尾部便可,直到整個壓縮文件處理完畢。

  lz77算法模型也被稱爲「滑動字典」模型或「滑動窗口」模型,因爲它限制匹配的最大長度,對於某些存在大量的極長匹配的文件來講,這種折衷算法顯出了缺陷。另有一種lzw算法對待壓縮文件中存在大量極長匹配的狀況進行了徹底不一樣的算法設計,而且只用一個數字來表示一段短語,下面來描述一下lzw的壓縮解壓過程,而後來綜合比較二者的適用狀況。

  lzw的壓縮過程:

1) 初始化一個指定大小的字典,把 256 種字節取值加入字典。

2) 在待壓縮文件的當前處理位置尋找在字典中出現的最長匹配,輸出該匹配在字典中的序號。

3) 若是字典沒有達到最大容量,把該匹配加上它在待壓縮文件中的下一個字節加入字典。

4) 把當前處理位置移到該匹配後。

5) 重複 二、三、4 直到文件輸出完畢。

  lzw 的解壓過程:

1) 初始化一個指定大小的字典,把 256 種字節取值加入字典。

2) 從壓縮文件中順序讀出一個字典序號,根據該序號,把字典中相應的數據拷貝到解壓文件尾部。

3) 若是字典沒有達到最大容量,把前一個匹配內容加上當前匹配的第一個字節加入字典。

4) 重複 二、3 兩步直到壓縮文件處理完畢。

  從 lzw 的壓縮過程,咱們能夠概括出它不一樣於 lz77 算法的一些主要特色:

1) 對於一段短語,它只輸出一個數字,即字典中的序號。(這個數字的位數決定了字典的最大容量,當它的位數取得太大時,好比 24 位以上,對於短匹配佔多數的狀況,壓縮率可能很低。取得過小時,好比 8 位,字典的容量受到限制。因此一樣須要取捨。)

2) 對於一個短語,好比 abcd ,當它在待壓縮文件中第一次出現時,ab 被加入字典,第二次出現時,abc 被加入字典,第三次出現時,abcd 纔會被加入字典,對於一些長匹配,它必須高頻率地出現,而且字典有較大的容量,纔會被最終完整地加入字典。相應地,lz77 只要匹配在「字典區域」中存在,立刻就能夠直接使用。

3) 一個長匹配被加入字典的過程,是從兩個字節開始,逐次增加一個字節,肯定了字典的最大容量,也就間接肯定了匹配的可能的最大長度。相對於 lz77 用兩個數字來表示一個短語,lzw 只用一個數字來表示一個短語,所以,「字典序號」的位數能夠取得多一點(二進制數多一位,意味着數值大一倍),也就是說最長匹配能夠比 lz77 更長,當某些超長匹配高頻率地出現,直到被完整地加入字典後,lzw將開始彌補初期的低效,逐漸顯出本身的優點。

  能夠看出,在多數狀況下,lz77 擁有更高的壓縮率,而在待壓縮文件中佔絕大多數的是些超長匹配,而且相同的超長匹配高頻率地反覆出現時,lzw 更具優點,GIF 就是採用了 lzw 算法來壓縮背景單1、圖形簡單的圖片。zip 是用來壓縮通用文件的,這就是它採用對大多數文件有更高壓縮率的 lz77 算法的緣由。

  接下來 zip 算法將要解決在「字典區域」中如何高速查找最長匹配的問題。

轉載自:http://csy8217.blog.163.com/blog/static/9446787200811111943266/

相關文章
相關標籤/搜索