1. 程式人生 > >GZIP壓縮原理分析(30)——第五章 Deflate演算法詳解(五21) 動態哈夫曼編碼分析(10)構建哈夫曼樹(02)

GZIP壓縮原理分析(30)——第五章 Deflate演算法詳解(五21) 動態哈夫曼編碼分析(10)構建哈夫曼樹(02)

*正規化哈夫曼編碼

使用靜態哈夫曼編碼的編碼/解碼雙方同時擁有一張完全相同的碼錶,這張碼錶是事先規定好的,只要使用這種壓縮方式並且使用這種壓縮方式對應的靜態哈夫曼編碼,那麼壓縮方就照著碼錶壓縮,解碼方照著碼錶直接解壓。就好比我們使用莫爾斯碼通訊,雙方在通訊之前就約定好使用莫爾斯碼(什麼時候約定的?可能是某次吃飯的時候,或者聊天的時候,或者一起喝酒的時候……),雙方都有一張莫爾斯碼錶,通訊的時候,一方將明文編碼成莫爾斯碼傳送出來,另一方收到資訊對著碼錶解碼即可。

動態哈夫曼編碼和靜態哈夫曼編碼不同,沒有固定的碼錶,編碼過程依賴於被編碼資料本身,被編碼資料不同,編碼結果就不同。就好比我們使用無線電通訊,通訊雙方A和B事前都沒有編碼/解碼用的碼錶,怎麼辦?A要根據自己要傳送的實際內容決定碼錶,實際內容不同,碼錶也不同。此時要想讓B把A傳送的資訊完整的解碼,A就必須把自己編碼的碼錶傳送給B,這樣B才能照著這張碼錶把收到的資訊解碼。A每次傳送的資訊不同,則每次傳送給B的碼錶也就不同,為了通訊方便,A就在每次通訊的時候把碼錶和對應的資訊一同傳送給B,B拿著這次發來的碼錶直接解碼這次發來的資料即可。這個過程其實就是動態哈夫曼編碼的編碼/解碼過程,A相當於壓縮,B相當於解壓,在壓縮的時候,要把碼錶記錄在壓縮結果中,解壓方從壓縮結果中把碼錶提取出來就可以解壓了。

這就說到關鍵了,既然壓縮要把碼錶記錄在壓縮結果中,那怎麼記錄,以什麼方式記錄?如果碼錶佔用的空間較大,整個壓縮結果比壓縮之前還大,那壓縮還有什麼意義!我們知道,哈夫曼編碼過程要使用到一棵二叉樹,就是哈夫曼樹,利用這棵樹就可以得到最終的哈夫曼編碼的碼字。壓縮並沒有直接將碼錶記錄在壓縮結果中,而是把構建這棵動態哈夫曼樹的資訊記錄在了碼錶,解壓方通過這些資訊在解壓本地將動態哈夫曼樹構建出來,再根據這棵樹就可以得到解壓所需的碼錶。只在壓縮結果中記錄構建哈夫曼樹的資訊,要比記錄整個動態哈夫曼碼錶簡單得多,而且整個壓縮結果不至於太大。

這就又牽扯出另外一個問題:構建哈夫曼樹的資訊包括什麼?前面預備知識部分我們已經說了壓縮使用的哈夫曼編碼和原始哈夫曼編碼不同,壓縮使用的哈夫曼編碼被稱為“正規化哈夫曼編碼”。注意正規化哈夫曼編碼的定義“……使用某些強制的約定,僅通過很少的資料便能重構出

哈夫曼編碼樹的結構”,正是正規化哈夫曼編碼的這些強制約定,簡化了我們記錄構建哈夫曼樹所需的資訊,這也就是為什麼壓縮要使用正規化哈夫曼編碼而不是原始哈夫曼編碼的原因。還記得預備知識中我們提到的那幾條哈夫曼樹的性質嗎?沒錯,那幾條性質就是壓縮所用哈夫曼樹的“強制約定”,只不過這些強制約定是以數學的形式表述的。簡單來說,壓縮使用的哈夫曼樹是一棵不平衡樹,樹的右側始終比左側要深!如下圖所示,

左側的哈夫曼樹很顯然沒有保證樹的右側始終比左側深,所以不能給壓縮使用;右側的哈夫曼樹能夠保證這一點,所以可供壓縮使用。

上圖中的兩棵哈夫曼樹如果單純從原始哈夫曼編碼的角度來看,並沒有什麼本質區別。這兩棵樹每個對應的葉子節點的碼字長度都相同(比如左邊哈夫曼樹的f葉子節點和右邊哈夫曼樹的f葉子節點,雖然哈夫曼樹不同,但是葉子節點的碼字長度是相同的),而且都是字首碼,不同的僅僅是某幾個葉子節點的具體碼字不同而已,但這點差別根本無關痛癢。當壓縮強制規定哈夫曼樹必須使用形如上圖右側的哈夫曼樹時,只要確定每個葉子節點的深度(樹深,或者說是碼字長度),就可以按部就班地將這棵“正規化”哈夫曼樹構建出來了。具體構造過程後面介紹。

總結,我們使用正規化哈夫曼樹來完成壓縮的哈夫曼編碼;壓縮結果通過記錄構造哈夫曼樹的資訊來間接告訴解壓方碼錶;構造樹的資訊就是葉子節點的深度(稱樹深或碼字長度都行),而且為了儘量減小壓縮結果尺寸,構造樹的資訊只有碼字長度。