計算機演算法:資料壓縮之圖編碼和模式替換(3)

maple發表於2014-11-19

概述

遊程編碼有兩種變形,分別是“圖編碼”和“模式替換演算法”。圖編碼是一種非常簡單的演算法,它不像遊程編碼那樣必須要求輸入字串中必須含有很多重複的元素,比如“aaaaaa”。這些情況在自然語言中很少出現,但是被稱之為“圖”的字串幾乎會出現在所有自然語言中。日常英語中就有些圖,例如“the”“and”“ing”(例如在“waiting”中),“aa”“tt”“ee”這些雙字母。事實上我們可以通過新增字串兩邊的空格來擴充套件這些圖。這樣我們可以不編碼“the”,而編碼“ the ”,就可以編碼5個字元了(3個字元,2個空格),從而獲得更好的壓縮率。另一方面,在日常英語中有很多包含雙字母的字串,而這些情況在遊程編碼中並不能有效地被壓縮,這樣導致壓縮效率非常低。有時候會有更加糟糕的情況出現,壓縮後的長度比原來的長度還要長。我們看一些例子:

我們談一談怎麼編碼這個包含了四對雙字母的字串“successfully accomplished”。很不幸,如果用遊程編碼的話,壓縮這四對雙字母至少需要八個字元,這意味著對壓縮資料來說沒有任何幫助。

這裡有個問題,如果原本的輸入字串中含有數字的話,比如“2”,那麼我們必須指定一個字元(比如“@”)用來描述我們編碼替換開始的位置。那麼如果輸入的字串為“2 successfully accomplished tasks”,壓縮之後就變為“2 su@2ce@2sfu@2ly a@2complished tasks”,這時候輸出的字串比輸入的字串還要長!

如果輸入的字串中包含轉義字元,我們必須找到另一個,然後問題就來了:若不進行全域性查詢,我們就很難找到另外一個出現在輸入字串中的轉義字元。

遊程編碼在壓縮純文字的時候並不是一個好的解決方案,因為長的重複情況(“aaaaa”)出現頻率太小了。當然,在失真壓縮的時候是一個特例。很明顯,當你要求解壓出來的文字需要是完全一致的時候,失真壓縮文字並沒有很大用處。但是有時候失真壓縮可能有用,比如在我們要去除空格的情況中。字串successfully           accomplished表達的意思和successfully accomplished確實完全相同。在這種情況下,我們能夠很簡單地去除那些空格。我們可以像這樣用一個標記去代替一長串的空格:successfully@6 accomplished,這樣可以使輸入字串完全沒有損失,但是我們也有另一種選擇,可以直接把那些空格直接扔掉。這個選擇取決於我們的目的是什麼,只有當我們很確定刪除換行標記符和製表標記符能夠完全保證表達出相同的意思時,才能下決定扔掉那些空格。但是話又說回來,現實中出現這種問題的情況也是少數,所以對純文字壓縮選擇圖壓縮方法比遊程編碼更好。

問題

在理解了圖編碼的基本規則之後,讓我們看一些例子。在上面的例子中,最好的替代雙字母的方法是,用“#”符號替代“cc”,用“@”替代“ss”,用“%”替代“ll”。這樣的話輸入字串就可以表示為“su#e@fu%y a#omplished”,這樣就可以比輸入字串短了。但是如果輸入字串中包含“# @ %”這些符號怎麼辦呢?而且我們也不能確定的說在輸入字串中有足夠多的雙字母讓我們有理由去替換它們。一個更好的方式是模式替換。

遊程編碼對普通文字壓縮並不是一個好的方法,因為長重複很少在自然語言中出現。

模式替換

模式替換演算法是圖編碼的一種變形。我在上面說過,在日常英語中“ the ”這種單詞會經常出現。我們現在可以把它替換為類似於“$%”這樣的字元。如果輸入字串是I send the message,那麼壓縮之後變成了I send$%message。但是這種方法也有一些缺陷。

第一個問題是我們需要知道了解將要被壓縮的這門語言,而且我們要定義出來哪些模式是經常被使用的。如果一條資訊用我們完全不懂的語言寫的。比如下面的拉丁文:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras venenatis, sapien eget suscipit placerat, justo quam blandit mauris, quis tempor ante sapien sodales augue. Praesent ut mauris quam. Phasellus scelerisque, ante quis consequat tristique, metus turpis consectetur leo, vitae facilisis sapien mi eu sapien. Praesent vitae ligula elit, et faucibus augue. Sed rhoncus sodales dolor ut gravida. In quis augue ac nulla auctor mattis sed sed libero. Donec eget purus eget enim tempor porta vitae eget diam. Mauris aliquet malesuada ipsum, non pulvinar urna vestibulum ac. Donec feugiat velit vitae nunc cursus imperdiet. Donec accumsan faucibus dictum. Phasellus sed mauris sapien. Maecenas mi metus, tincidunt sed rhoncus nec, sodales non sapien.

很明顯,如果我們不懂拉丁文,那麼定義出來一個常用模式會十分困難。所以如果我們事先知道一些單詞或者字母的話,會讓我們更方便運用模式替換。

第二個問題是關於解壓縮。我們需要構造一個用來解壓縮的字典。如果我們能夠找到比三個字母長的模式會非常好。但是如果找不到,那麼壓縮率會比較低。而不幸的是,這種長的(3個字元以及3個以上的)模式在自然語言中很少出現。

在壓縮純文字的情況下,圖編碼和模式替換都要優於遊程編碼。而且,模式替換在壓縮程式語言的情況下十分高效。

應用

一個很有趣的問題是,怎麼使用圖編碼和模式替換演算法壓縮純文字,特別是當我們不太瞭解被壓縮文字使用的語言時。答案其實就在問題中,我們可以不壓縮自然語言,轉而壓縮程式語言。因為程式語言限制了一個有限的小單詞和符號集合。它幾乎對任何程式語言都適用。比如PHP,“function”“while”“for”“break”“switch”“foreach”都是經常用的,或者在HTML中,有很多常用的標籤。也許最好的例子是CSS,在CSS中只有屬性值能夠變化。CSS檔案中也經常有很多為了提高可讀性的換行符,製表符以及空格。

這裡就有一個問題,很明顯壓縮之後的檔案對程式設計師和計算機來說都完全不能發揮作用了,那麼為什麼我們要壓縮這些檔案呢。確實是這樣,但是如果我們要把不同版本的檔案存入資料庫作為備份呢。想象一下你正在為一個網站託管公司工作,每天要儲存大量託管網站的備份檔案。即使只是一個小的網站託管網站上面只有少量的託管網站,那備份檔案也是一個驚人的數量。用一個簡易壓縮工具壓縮那些檔案顯然不是一個好主意。我們每天都必須要備份整個網站,但是我們知道每天網站的版本更新的地方非常少。版本控制系統是一個解決方案,但是你必須去儲存那些純文字檔案。

也許一個好方法是運用相對編碼:用模式替換去壓縮這些文字,而且只記錄變化更新的部分——類似於版本控制。

運用上面的方法我們能夠節省很多磁碟空間,而且我們能夠很方便進行壓縮和解壓縮。另外一個好處是你可以類似於版本控制把所有的更改儲存到原始的檔案中,而這個檔案也可以被壓縮。

實現

這個演算法的實現也是基於PHP,希望能夠描述這個壓縮演算法的主要思想,在這個例子中我試圖利用上面的壓縮方法壓縮一個CSS檔案。雖然這個例子非常簡單,但是我們仍然能看到很多有趣的地方。首先你只需要一個壓縮和解壓縮的字典。在實際中這個壓縮過程和解壓縮過程是相同的,所以你不需要去寫兩個不同的函式。在這裡用了一個原生的PHP函式“str_replace”[1],因為這個演算法的目的不是介紹實現模式替換的技術細節,而是描述模式替換的思想。所以我們假定程式語言有操作字串的特定函式。


[1] 函式原型:mixed str_replace(mixed $search, mixed $replace, mixed $subject [, int $count])

功能:所有在subject中出現的search字串替換成replace

我僅僅替換了少數幾個CSS的屬性,但是我就已經得到了40%的壓縮率(見下圖)!初始檔案大小為202KB,但是壓縮之後只有131KB(當然它主要是依賴了CSS檔案的特性)。如果我把所有屬性都替換成短字元呢。那樣的壓縮率將會更高。

相關文章