一本正經的聊資料結構(7):哈弗曼編碼

極客挖掘機發表於2020-06-03

前文傳送門:

「一本正經的聊資料結構(1):時間複雜度」

「一本正經的聊資料結構(2):陣列與向量」

「一本正經的聊資料結構(3):棧和佇列」

「一本正經的聊資料結構(4):樹」

「一本正經的聊資料結構(5):二叉樹的儲存結構與遍歷」

「一本正經的聊資料結構(6):最優二叉樹 —— 哈夫曼樹」

引言

在上一期,我們介紹了什麼是哈夫曼樹以及哈夫曼樹的構建過程,本期我們接著介紹哈夫曼樹的用途。

字元編碼壓縮

哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通訊中的應用之一。廣泛地用於資料檔案壓縮的十分有效的編碼方法,其壓縮率通常在 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」 ,則向右走,一旦到達葉子結點,則譯出一個字元。然後不停的重複,直到這個編碼的結束,就是我們需要的原內容了。

參考

https://www.cnblogs.com/wkfvawl/p/9783271.html

https://my.oschina.net/goldenshaw/blog/307708

相關文章