lzw 壓縮算法的原理與細節思考

lzw 是一種無損數據壓縮算法。
lzw 壓縮原理:
爲了簡化問題,下面用的是僞代碼:

1.首先初始化一個“字典”,“字典”裏包含了 128 個 ASC II 碼。

  var dictionary = new Array;
  for(i = 0; i < 128; i++)
  {
    dictionary[i]=String.fromCharCode(i);
  }

2.不斷地在輸入文件中尋找在字典中出現的最長的匹配p,並輸出其在字典中的位置值到目的文件。若輸入文件中下一個字符爲c,把pc插入字典。
 
  StringInDictionary = input_first_char();

  while( ! AtEndOfFile )
  {

    if( search_dictionary(StringInDictionary) ) != null)
    {
        CodeInDictionary = search_dictionary(StringInDictionary);

        NextChar = input_next_char();
        StringInDictionary += NextChar;
    }
    else
    {
        Output(CodeInDictionary);
        dictionary[dictionary.length] = StringInDictionary;
        StringInDictionary = NextChar;
    }
  }


  /*在字典裏搜索特定字符串*/

  function search_dictionary(str)
  {
    for( i = 0; i < dictionary.length; i ++ )
    {
        if( dictionary[i] == str )
          return i;
    }

    return null;
  }

這樣就得到了壓縮文件。
可以看出,壓縮文件裏並沒有包含字典,事實上,解壓縮時字典是可以根據壓縮文件裏的內容重建的。
下面我們來看一下解壓縮的代碼:

  var dictionary = new Array;
  for(i = 0; i < 128; i++)
  {
    dictionary[i] = String.fromCharCode(i);
  }

  previous_code = ReadFirstCode();
  OutPutString = dictionary[previous_code];
  Output(OutPutString);

  while( ! AtEndOfFile )
  {
    current_code = ReadNextCode();
    OutPutString = dictionary[current_code];
    Output(OutPutString);
    dictionary[dictionary.length] = dictionary[previous_code] + OutPutString.substr(0, 1);
    previous_code = current_code;
  }

可以用這個原理來壓縮任何文件,gif 也是基於這樣的原理,只不過字典初始化時不是存儲 ASC II 碼,而是 256 種(或更少)預定義的顏色值。對於通用文件壓縮,字典初始化時存儲一個字節的所有可能的取值(0 到 255)。

一些細節問題有
1.字典數組的長度我們限制在 4096,這樣,字典的每個位置值,我們用 12 位的二進制整數就足夠表示了,輸出時,我們把 4 個位置值(48 位)拼湊成 3 個 unicode 字符(正好也是 48 位)輸出。
2.壓縮和解壓縮時,有可能最後一個代碼沒有被輸出,編程時需要注意,具體解決可以看我在前面發的完整程序。
3. 解壓縮時,有可能某些代碼在字典中找不到對應的解壓文本,通過仔細考察壓縮過程,可以知道這個代碼對應的文本是dictionary [previous_code] + dictionary[previous_code].substr(0, 1)。其中 previous_code 是此代碼的前一個代碼。

下面我們來看一個棘手的問題:
字典應該用什麼樣的數據結構來組織,以提高查找匹配串的效率。
爲什麼用哈希表來組織字典能有效減少程序的運算量,使搜索字典的速度比遍歷普通一維數組提高几千倍。

首先我們考察一下字典需要存儲哪些內容:

1.前面我們知道字典需要存儲 4096 個字符串(key),內容因輸入文件的不同而無法預知。
2.字典還需要存儲這些字符串相對應的編號(code),內容是 0 到 4095。
最直觀和最容易想到的是一維數組,像這樣:

dictionary[code] = key;

這樣的數組只能通過遍歷來搜索一個特定的 key,最壞的情況是 4096 個循環,考慮到輸入文件內容的隨機性,搜索一個 key 平均要循環兩千多次。

那麼哈希表是怎麼做的呢?
哈希表首先創建一些“桶”,再把元素分散到一個個的“桶”裏,根據元素的 key(而不是 code),就可以確定這個元素是在哪一個“桶”裏,然後去遍歷這個“桶”。
其中的關鍵是把元素分散到特定“桶”裏的規則,其實,這個規則是由你自己定義的,一個好的規則應該是:根據 key 能夠確定唯一的“桶”;分配儘可能做到均勻,能確實降低元素的密度(單個“桶”裏的元素儘可能少);規則的算法儘可能簡單,運算量越少越好。這個規則被稱爲哈希函數。

在我們的程序裏,哈希表初始化時創建了 4099 個“桶”,採用的哈希函數是:keyword % 4099
其中 keyword 是由 key 所包含的單個字符的 ASC II 編碼值拼接而成。
由於字典裏只要存儲 4096 個元素,所以“桶”裏的元素的平均密度小於 1。搜索一個 key 最壞的情況仍然只是 4096 個循環(出現這種情況幾乎不可能),而平均的循環次數降低到 1 次。

我們的“桶”是子數組,4099 個“桶”構成的二維數組就是我們的哈希表。

總結:hash 是分散的意思,哈希表又稱爲散列,它的實質是把元素按照自定的規則分散開存儲,以有效降低搜索的密度。


LZW壓縮算法簡介
作者:宋成
描述:一篇關於LZW壓縮算法簡介的文章,通俗易懂,值得一看!
備註:該文章整理自軟件報1998年合訂本上冊。


LZW 壓縮算法是一種新穎的壓縮方法,由Lemple-Ziv-Welch 三人共同創造,用他們的名字命名。它採用了一種先進的串表壓縮不,將每個第一次出現的串放在一個串表中,用一個數字來表示串,壓縮文件只存貯數字,則不存貯串,從而使圖象文件的壓縮效率得到較大的提高。奇妙的是,不管是在壓縮還是在解壓縮的過程中都能正確的建立這個串表,壓縮或解壓縮完成後,這個串表又被丟棄。

1.基本原理
首先建立一個字符串表,把每一個第一次出現的字符串放入串表中,並用一個數字來表示,這個數字與此字符串在串表中的位置有關,並將這個數字存入壓縮文件中,如果這個字符串再次出現時,即可用表示它的數字來代替,並將這個數字存入文件中。壓縮完成後將串表丟棄。如 "print" 字符串,如果在壓縮時用266表示,只要再次出現,均用266表示,並將"print"字符串存入串表中,在圖象解碼時遇到數字266,即可從串表中查出 266所代表的字符串"print",在解壓縮時,串表可以根據壓縮數據重新生成。

2.實現方法
A.初始化串表
在壓縮圖象信息時,首先要建立一個字符串表,用以記錄每個第一次出現的字符串。一個字符串表最少由兩個字符數組構成,一個稱爲當前數組,一個稱爲前綴數組,因爲在 GIF文件中每個基本字符串的長度通常爲2(但它表示的實際字符串長度可達幾百甚至上千),一個基本字符串由當前字符和它前面的字符(也稱前綴)構成。前綴數組中存入字符串中的首字符,當前數組存放字符串中的尾字符,其存入位置相同,因此只要確定一個下標,就可確定它所存貯的基本字符串,所以在數據壓縮時,用下標代替基本字符串。一般串表大小爲4096個字節(即2 的12次方),這意味着一個串表中最多能存貯4096個基本字符串,在初始化時根據圖象中色彩數目多少,將串表中起始位置的字節均賦以數字,通常當前數組中的內容爲該元素的序號(即下標),如第一個元素爲0,第二個元素爲1,第15個元素爲14 ,直到下標爲色彩數目加2的元素爲止。如果色彩數爲256,則要初始化到第258個字節,該字節中的數值爲257。其中數字256表示清除碼,數字257 爲圖象結束碼。後面的字節存放文件中每一個第一次出現的串。同樣也要音樂會前綴數組初始化,其中各元素的值爲任意數,但一般均將其各位置1,即將開始位置的各元素初始化爲0XFF,初始化的元素數目與當前數組相同,其後的元素則要存入每一個第一次出現的字符串了。如果加大串表的長度可進一步提高壓縮效率,但會降低解碼速度。

B.壓縮方法
瞭解壓縮方法時,先要了解幾個名詞,一是字符流,二是代碼流,三是當前碼,四是當前前綴。字符流是源圖象文件中未經壓縮的圖象數據;代碼流是壓縮後寫入GIF 文件的壓縮圖象數據;當前碼是從字符流中剛剛讀入的字符;當前前綴是剛讀入字符前面的字符。
GIF 文件在壓縮時,不論圖象色彩位數是多少,均要將顏色值按字節的單位放入代碼流中,每個字節均表示一種顏色。雖然在源圖象文件中用一個字節表示16色、4 色、2色時會出現4位或更多位的浪費(因爲用一個字節中的4位就可以表示16色),但用LZW 壓縮法時可回收字節中的空閒位。在壓縮時,先從字符流中讀取第一個字符作爲當前前綴,再取第二個字符作爲當前碼,當前前綴與當前碼構成第一個基本字符串(如當前前綴爲A,當前碼爲B則此字符串即爲AB),查串表,此時肯定不會找到同樣字符串,則將此字符串寫入串表,當前前綴寫入前綴數組,當前碼寫入當前數組,並將當前前綴送入代碼流,當前碼放入當前前綴,接着讀取下一個字符,該字符即爲當前碼了,此時又形成了一個新的基本字符串(若當前碼爲C,則此基本字符串爲BC),查串表,若有此串,則丟棄當前前綴中的值,用該串在串表中的位置代碼(即下標)作爲當前前綴,再讀取下一個字符作爲當前碼,形成新的基本字符串,直到整幅圖象壓縮完成。由此可看出,在壓縮時,前綴數組中的值就是代碼流中的字符,大於色彩數目的代碼肯定表示一個字符串,而小於或等於色彩數目的代碼即爲色彩本身。

C.清除碼
事實上壓縮一幅圖象時,常常要對串表進行多次初始化,往往一幅圖象中出現的第一次出現的基本字符串個數會超過4096個,在壓縮過程中只要字符串的長度超過了4096,就要將當前前綴和當前碼輸入代碼流,並向代碼流中加入一個清除碼,初始化串表,繼續按上述方法進行壓縮。

D.結束碼
當所有壓縮完成後,就向代碼流中輸出一個圖象結束碼,其值爲色彩數加1,在256色文件中,結束碼爲257。

E.字節空間回收
在GIF 文件輸出的代碼流中的數據,除了以數據包的形式存放之外,所有的代碼均按單位存貯,樣就有效的節省了存貯空間。這如同4位彩色(16色)的圖象,按字節存放時,只能利用其中的4位,另外的4位就浪費了,可按位存貯時,每個字節就可以存放兩個顏色代碼了。事實上在GIF 文件中,使用了一種可變數的存貯方法,由壓縮過程可看出,串表前綴數組中各元素的值頒是有規律的,以256色的GIF文件中,第258-511元素中值的範圍是0-510 ,正好可用9位的二進制數表示,第512-1023元素中值的範圍是0-1022,正好可用10位的二進制數表示,第1024-2047 元素中值的範圍是0-2046,正好用11位的二進制數表示,第2048-4095元素中值的範圍是0-4094,正好用12位的二進制數表示。用可變位數存貯代碼時,基礎位數爲圖象色彩位數加1,隨着代碼數的增加,位數也在加大,直到位數超過爲12(此時字符串表中的字符串個數正好爲2 的12次方,即4096個)。其基本方法是:每向代碼流加入一個字符,就要判別此字符所在串在串表中的位置(即下標)是否超過2的當前位數次方,一旦超過,位數加1。如在4位圖象中,對於剛開始的代碼按5位存貯,第一個字節的低5位放第一個代碼,高三位爲第二個代碼的低3位,第二個字節的低2位放第二個代碼的高兩位,依次類推。對於8 位(256色)的圖象,其基礎位數就爲9,一個代碼最小要放在兩個字節。

F.壓縮範圍
以下爲256色GIF文件編碼實例,如果留心您會發現這是一種奇妙的編碼方法,同時爲什麼在壓縮完成後不再需要串表,而且還在解碼時根據代碼流信息能重新創建串表。
字 符 串: 1,2,1,1,1,1,2,3,4,1,2,3,4,5,9,…
當 前 碼: 2,1,1,1,1,2,3,4,1,2,3,4,5,9,…
當前前綴: 1,2,1,1,260,1,258,3,4,1,258,262,4,5,…
當前數組: 2,1,1, 1, 3,4,1, 4,5,9,…
數組下標: 258,259,260,261,262,263,264,265,266,267,…
代 碼 流: 1,2,1,260,258,3,4,262,4,5,…

GIF文件作爲一種重要的圖形圖象文件格式,儘管其編碼規則極複雜,但其壓縮效率是極高的,特別是對某些平滑過渡的圖象的圖形,壓縮效果更好。同時由於其在壓縮過程中的對圖象信息能夠完整的保存,在目前流行的電子圖片及電子圖書中得到了廣泛的應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章