深入解析數據壓縮算法
前序
開始本文之前,先回顧一下上篇。上篇講解了幾種數據壓縮算法中的兩種:Huffman壓縮算法和RLE壓縮算法。
詳解數據壓縮算法(上):http://blog.csdn.net/fengchaokobe/article/details/7934865
正文
本文將詳解數據壓縮算法的後兩種算法:Rice壓縮算法、LZW壓縮算法。
第一節 LZW壓縮算法
LZW壓縮算法:Lempel-Ziv-Welch Encoding。算法的命名簡單直接,故而LZW壓縮算法也跟隨了算法命名的特性:簡單易懂!---有點扯皮了,開始幹正事!
LZW壓縮算法的實現就是通過在編碼的過程中建立一個字符串表,並用某個數字來表示這個串,壓縮文件只存儲那個數字。這些數字就是壓縮後的數據。
LZW算法的實現原理:由於源數據字符串中一定會出現重複的字符串,於是我們就利用這點,將第一次出現的串組合用某一數字表示,然後將這個數字保存起來。那麼當再次遇見這個串時就可直接用這個數字來表示,以此來達到壓縮的目的。所以說,能正確的得到這個數字表是壓縮成功的關鍵。
從原理中我們可得知:在源數據中,如果重複的字串越多,那麼壓縮的效果就越好。
好了,理論說的再多也是空談,既然瞭解了,那我們就上例子來說明。
現有如下的源數據串,我們現用LZW壓縮算法來操作實現壓縮:
戰前準備:
1.原理中提到我們用某一數字來表示壓縮的結果,那麼這些數字的起始怎麼選擇呢?這時我們就要根據LZW壓縮算法的應用來說了。LZW主要應用在圖像的處理上,如果圖像的色彩數爲256,那我們就要從258開始(其中256表示清除碼,257表示圖像結束碼)。
2.編碼過程中一些聲明:
源數據:壓縮的目標數據;
索引數組:編碼後字符串表所對應數字表的匹配數組。
匹配結果:Yes | No。
編碼字符表:經過編碼得到的結果。
數字表標號:當有新的編碼生成時會得到相應的標號。
萬事俱備,進入狀態:
編碼過程中須遵守如下的規則:
編碼的過程:
從表中我們可知:編碼字符表中的字符和數字就是我們LZW編碼的結果,我們只需將字符串表和數字表一一對應即可,最後我們只保留數字串。
好了,這就是LZW壓縮算法的編碼過程。不過還有一個問題需要解決,就是編碼的標號是從258開始,那麼從多少結束呢?顯然,我們不可能讓其一直增大下去!於是,我們就規定一下:當標號達到4096時(GIF規範規定的是12位,超過12位的表達範圍就得重來),我們就將整個標號集重新初始化,開始使用新的標記,提高了利用率和效率,何樂而不爲呢!
LZW壓縮算法就講完了,算法的實現過程大致的說明白了,不過有些細節還是沒有講到,以後如果遇見了一定會在文中補充!
最後一項,LZW編碼算法的核心代碼:
/***這個結構體是爲了用結構體的成員表示編碼串和數字值***/ typedef struct Dictionary{ int value; /**編碼的值**/ unsigned char prefix_string; /**與prefix作比較**/ unsigned char char_add; /**與suffix作比較**/ }Dictionary; Dictionary dict[MaxLength]; /**MaxLength = 4096**/
/***LZW壓縮算法的實現過程***/ int * lzw_coding(unsigned char *src_ch, unsigned int *Prefix_Suffix, int src_length, unsigned int *Char_Stream) { /*** src_ch表示源數據串 *** Prefix_Suffix表示索引數組 *** src_length表示源數據串的長度 *** Char_Stream表示經過編碼後的數字表 ***/ int i = 0; int j = 0; int k = 0; int temp = 0; int code = 258; // 從258開始 while(i < src_length) { Prefix_Suffix[j+1] = src_ch[i]; //源數據賦值給suffix if(Prefix_Suffix[j] == 0) { Prefix_Suffix[j] = Prefix_Suffix[j+1]; i++; continue; } temp = compare(Prefix_Suffix[j], Prefix_Suffix[j + 1]); if(dict[temp].value != UNUSED) //在串表中可以找到,UNUSED = -1 { Prefix_Suffix[j] = dict[temp].value; //如若相同變索引 } else //串表中找不到,如若不同則編碼 { dict[temp].value = code++; dict[temp].prefix_string = Prefix_Suffix[j]; dict[temp].char_add = Prefix_Suffix[j+1]; Char_Stream[k++] = Prefix_Suffix[j]; Prefix_Suffix[j] = Prefix_Suffix[j + 1]; } i++; } return Char_Stream; }
/***比較索引數組的串是否在串表中出現過***/ int compare(unsigned char prefix, unsigned char suffix) { int i = 0; i = prefix % MaxLength; while(1) { if(dict[i].value == UNUSED) { return i; } if(dict[i].prefix_string = prefix && dict[i].char_add == suffix) { return i; } i++; } }
參考文獻:http://tech.watchstor.com/management-115343.htm
第二節 Rice壓縮算法
Rice編碼:Rice encoding,是由Robert F. Rice發明的這個算法,我們直譯過來就叫它“大米編碼”。
Rice壓縮算法的基本思想是:用較少的位來表示多個字(或數字),更重要的是,它能區分當前字(或數字)和下一個字(或數字)的位置。
有些人稱RICE壓縮算法是靜態的Huffman編碼算法,還是很有道理的。
附註:Rice壓縮算法一般都是對較小的數字進行操作,因爲數字越小,它需要的位就越少。
Rice壓縮算法的原理:被除數 =除數 * 商 +餘數。經過“大米”壓縮算法壓縮之後的結果就是由除數和餘數組成的。
我先詳細的解釋一下這個原理:
令 S = Q * M + R(Q和R的組合將會是S的壓縮結果),壓縮結果的表示:
S:爲將要壓縮的數;
M:是一個常數,M = 2K;
Q:Q = S >> K;
R:R = S & (M - 1),R用K位表示。
其中對於K,K表示一個數的位數,而這個K是由一些數的平均係數來決定的。比如現有:10,12,14,18,36這五個數,你會發現前三個數的平均位數爲4,於是這個K就是4了。也就是說,在某一範圍內,一些數的出現次數較多,且這些數可用K位來表示,那麼K就定下來了。
好了,原理說明白了,我們用一個例子實現編碼的過程:
在“大米”壓縮之前,我先說明一下常規方法,這樣就可以和“大米”壓縮算法作比較了,更加清晰的理解。
常規情況下,我們對上述五個數編碼,即就是:1010 1100 1110 10010 100100。試想想,如果這樣的話,解碼的時候你怎樣區分當前數和下一個數呢?這就比較麻煩了。所以,Rice算法正好解決了這個問題。
現對10,12,14,18,36這五個數用“大米”壓縮算法進行編碼,在原理中已經說明如何得到K,於是K = 4。那麼就有:
首先,對於10,12,14這三個數可直接用4位表示,即1010,1100,1110。解碼的時候已知K = 4,所以很容易。
接下來,對於18,可表示爲:10010,根據編碼原理:
M = 2K= 16;
Q = S >> K = 18 >> 4 = 10010 >> 4 = 0b0001;Q轉爲十進制是1,根據表示方法,1的後面跟1個0,即10;
R = S & (M - 1) = 18 & (16 - 1) = 10010 & 1111 = 0b0010
於是,壓縮結果就是:100010。
最後,對於36,表示爲:100100,根據編碼原理:
M = 2K= 16;
Q = S >> K = 36 >> 4 = 100100 >> 4 = 0b0010;Q轉爲十進制的值是2,根據表示方法,1的後面跟2個0;
R = S & (M - 1) = 36 & (M - 1) = 100100 & 1111 = 0b0100
於是壓縮結果就是:1000100。
怎麼樣,挺容易吧,這就是“大米”壓縮算法。該算法的關鍵就是能辨別出當前數和下一個數的位置!解壓縮的過程更簡單了,我們已知K的值和Q與R的規範,直接解壓就OK!
好了,最後就是壓縮和解壓縮的算法:
壓縮: char * Rice_coding(char src) { if(src & 0xf0 == 0) //可直接用K位表示,標誌位置0 { printf 直接輸出K位; } else //超過K位,標誌位置1 { Q = SRC >> K; temp_Q = (int)(Q & 0XFF); R = src & (M - 1); printf 1 + temp_Q + R; } }
解壓縮: char Rice_decoding(char *src) { if(標誌位爲0) //源數據可用K位表示 { 直接取K位還原 } else //標誌位爲1 { 取Q的值,Q已知(從壓縮過程中獲得); R串 = src - Q串; S = Q × M + R; } }
第三節 結束語
想想、寫寫、畫畫......