前文傳送門:
引言
在上一期,我們介紹了什么是哈夫曼樹以及哈夫曼樹的構建過程,本期我們接着介紹哈夫曼樹的用途。
字符編碼壓縮
哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通信中的應用之一。廣泛地用於數據文件壓縮的十分有效的編碼方法,其壓縮率通常在 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」 ,則向右走,一旦到達葉子結點,則譯出一個字符。然后不停的重復,直到這個編碼的結束,就是我們需要的原內容了。