前文傳送門:
引言
在上一期,我們介紹了什麼是哈夫曼樹以及哈夫曼樹的構建過程,本期我們接著介紹哈夫曼樹的用途。
字元編碼壓縮
哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通訊中的應用之一。廣泛地用於資料檔案壓縮的十分有效的編碼方法,其壓縮率通常在 20% ~ 90% 之間。
在電訊通訊業務中,通常用二進位制編碼來表示字母或其他字元,並用這樣的編碼來表示字元序列。
在計算機當中,因為計算機不是人,不能識別影像、聲音、視訊等內容,對於計算機來講,它只能認識二進位制的 0 和 1 ,在數位電子電路中,邏輯閘的實現直接應用了二進位制,因此現代的計算機和依賴計算機的裝置裡都用到二進位制。
我們在計算機上看到的一切的影像、聲音、視訊等內容,都是由二進位制的方式進行儲存的。
簡單來講,我們把資訊轉化為二進位制的過程可以稱之為編碼,在計算機的世界裡,編碼有很多種格式,比如我們常見的: ASCII 、 Unicode 、 GBK 、 GB2312 、 GB18030 、 UTF-8 、 UTF-16 等等。
編碼方式從長度上來分大致可以分為兩個大類:
- 定長編碼:定長僅表明段與段之間長度相同,但沒說明是多長。
- 變長編碼:變長就是段與段之間的長度不相同,同樣也不定義具體有多長。
在最初的設計中, ASCII 編碼就是採用的定長編碼的方案,使用定長一位元組來表示一個字元。
舉個例子,假如我們對 「hello」 進行編碼,使用定長編碼,為了方便,採用了十進位制,主要是因為我懶,原理與二進位制是一樣的。
字元 | 編碼 |
---|---|
h | 00 |
e | 01 |
l | 02 |
o | 03 |
假設我們現在有個檔案,內容是 00000001 ,假如定長 2 位(這裡的位指十進位制的位)是唯一的編碼方案,用它去解碼,就會得到 「hhhe」 (可以對比上面的編碼, 00 代表 h ,所以前 6 個 0 轉化成 3 個 h ,後面的 01 則轉化成 e )。
但是,如果定長 2 位不是唯一的編碼方案呢?如上圖中的定長 4 位方案,如果我們誤用定長 4 位去解碼,結果就只能得到「he」( 0000 轉化為 h , 0001 轉化為 e )
隨著時代的發展,不僅老美要對他們的英文進行編碼,我們中國人也要對漢字進行編碼,而早期的 ASCII 碼的方案只有一個位元組,對我們漢字而言是遠遠不夠的,所以在我們的漢字編碼方案 GB2312 中,漢字是使用兩個位元組來表示的(這也是迫不得已的事,一位元組壓根不夠用) 。
再多說一句,實際上我們的 GB2312 是一種變長的編碼方案,主要是為了相容一個位元組的 ASCII 碼。
隨著計算機在全世界的推廣,各種編碼方案都出來了,彼此之間的轉換也帶來了諸多的問題。採用某種統一的標準就勢在必行了,於是乎天上一聲霹靂, Unicode 粉墨登場!
不過 Unicode 對於只需要使用到一個位元組的 ASCII 碼來講,讓他們使用 Unicode ,多少還是不是很願意的。
比如 「he」 兩個字元,用 ASCII 只需要儲存成 6865 ( 16 進位制),現在則成了 00680065 ,前面多的那兩個 0 (用作站位) ,基本上可以說是毫無意義,用 Unicode 編碼的文字,原來可能只有 1KB 大小,現在要變成 2KB ,體積成倍的往上漲。
最終, Unicode 編碼方案逐漸演化成了變長的 UTF-8 編碼方案,並且 UTF-8 是可以和 ASCII 碼進行相容。
UTF-8 因為能相容 ASCII 而受到廣泛歡迎,但在儲存中文方面,要用 3 個位元組,有的甚至要 4 個位元組,所以在儲存中文方面效率並不算太好,與此相對, GB2312 , GBK 之類用兩位元組儲存中文字元效率上會高,同時它們也都相容 ASCII ,所以在中英混合的情況下還是比 UTF-8 要好,但在國際化方面及可擴充套件空間上則不如 UTF-8 了。
所以如果有進軍國際的想法,那麼最好還是使用 UTF-8 編碼。
哈弗曼編碼
哈弗曼編碼是一種不定長的編碼方式,是由麻省理工學院的哈夫曼博所發明,這種編碼方式實現了兩個重要目標:
-
任何一個字元編碼,都不是其他字元編碼的字首。
-
資訊編碼的總長度最小。
乾巴巴的,還是接著舉例子:
如果我們對 「ABACCDA」 進行編碼,假設 A, B, C, D 的編碼分別為 00, 01,10, 11。
那麼 「ABACCDA」 編碼後的結果是 「00010010101100」 (共 14 位),我們解碼的時候只需要每兩位進行拆分,就可以恢復編碼前的資訊了。
那我們如果用哈弗曼編碼的方式進行編碼呢?
第一件事情是要確定每個字母的權值(出現頻率), 「ABACCDA」 這個字串中 A, B, C, D 的權值(出現的頻率)分別為 0.43, 0.14, 0.29, 0.14 。
有了權值,我們可以構造一個哈弗曼樹了,感興趣的同學可以自己畫一下,下面這個是我畫的:
編碼的結果就顯而易見了: A:0, C:10, B:110, D:111 。
剛才那個 「ABACCDA」 編碼後的結果就是 「0110010101110」 (共 13 位)。
上面我們知道了哈夫曼編碼如何編碼,那麼我們拿到了一個經過哈弗曼編碼後的程式碼,如何進行譯碼工作呢?
首先還是要知道每個字母的權重是多少,然後畫出來這個哈弗曼樹,接下來,就可以對照著這個哈弗曼樹進行譯碼工作了。
在譯碼的過程中,若編碼是 「0」 ,則向左走。若編碼是 「1」 ,則向右走,一旦到達葉子結點,則譯出一個字元。然後不停的重複,直到這個編碼的結束,就是我們需要的原內容了。