用 Huffman 樹實現檔案壓縮並解壓

發表於2016-11-25

一、前言

如果你學習資料結構,就一定會學到Huffman樹,而Huffman編碼實際上上就是zip壓縮的核心部分,所以,如果已經學習了Huffman樹,為何不嘗試寫一個壓縮程式出來呢?

如果你沒有學習Huffman樹,那我們們就一起先學習一下Huffman樹吧。

二、Huffman 樹壓縮檔案

定義:Huffman樹,又稱為最優二叉樹,是加權路徑長度最短的二叉樹。

建立:

這樣建立的樹,保證所有資料成員都在葉子節點上,且數越小,離根節點越遠,越大,離根節點越近,那麼這樣的特點應用於壓縮中是很關鍵的,我們可以讓出現次數少的字元編碼長一些,次數多的字元編碼短一些。接下來我們看看壓縮的步驟吧~

1>統計要壓縮的檔案中字元出現的次數。

遍歷一遍檔案,將字元出現的次數統計在一個結構體陣列裡,陣列裡包含字元,字元出現的次數,對該字元的編碼。

2>用得到的陣列構建一個Huffman樹。

因為每次要取最小值,所以這裡考慮建立一個小堆。

3>得到Huffman編碼

怎麼得到呢?向右為1,向左為0,就是這麼簡單,我畫圖示意一下:

原本用一個char表示的字元,現在只佔了幾個位,這就是為什麼能將檔案壓縮。

4>向壓縮檔案裡寫入Huffman編碼。

寫入的時候,滿8個位寫進去,如果最後不足8個位,先補齊,解壓的時候要注意,解壓到原始檔字元數的時候停止即可。原始檔的總字元數可以在第一次遍歷統計出現的字元個數時統計,還有一種方法就是,仔細觀察Huffman樹就知道,它的根節點的大小,其實就是所有葉子節點相加的和。所以,根節點的大小就是原始檔裡所有字元出現的總次數。

至此,壓縮就結束了。

但是,怎麼解壓縮呢?解壓縮至少也得已知這樣的一顆樹才行啊,所以,我們在壓縮完成後要建立一個配置檔案。

5>建立配置檔案

配置檔案裡要儲存原始檔字元及出現的次數。有了這樣的配置檔案,就可以再次構建Huffman樹!

三、解壓縮

1>讀取配置檔案,重新構建Huffman樹

2>讀取壓縮檔案

由壓縮時的原理可知,此時讀到1指標向右移動,0向左移,到葉子節點停下,將字元還原。不停的迴圈,直到檔案結束或者總字元數變為0.這裡就能體現出,Huffman壓縮是一種無損的壓縮,如果程式碼沒有問題,它會原原本本的還原原始檔。

解壓到這裡成功。可以先使用小檔案測試,若沒有問題則找個大點的檔案,還有各類格式的檔案都拿來壓一壓測一下。

四、我遇到的問題

1>編譯時不通過,一大堆的錯誤,我找了半天!最後發現是一個很簡單的問題,我的Huffman樹使用的是C++模板實現的,模板不能分離編譯,而我在壓縮時建立Huffman樹是在另一個檔案中進行的,所以編譯不通過。

解決方法:.h字尾改成.hpp,重新包一下標頭檔案ok。

2>檔案的開啟方式。這裡開啟檔案一定要用二進位制形式,”wb”,”rb”.因為二進位制開啟和文字開啟其實是有區別的。文字方式開啟,會對‘n’進行特殊處理,那如果這個字元本身就是’n’.這就會出現問題,所以使用二進位制開啟,特點:不進行任何處理,是什麼就是什麼。

3>壓縮後解壓縮的圖片打不開,經過我反覆查詢,終於發現是配置檔案裡對‘’的處理問題,我在寫配置檔案起初是用一個string把字元和它出現的次數連線起來放進去。比如:a,3   這樣帶來的問題是  ,200  寫的時候是以c字串的形式寫的,所以遇見”就終止了,那麼在解壓縮的時候就會出問題。

解決方法:先把字元放進去,再把剩下的構建成string物件放進去。

五、原始碼

1>Huffman樹

2>壓縮和解壓縮

最後,檔案大了之後怎麼對比兩個檔案是否一致呢?我用的是beyond Compare這個軟體,很方便,能對比各種型別的檔案。

相關文章