前兩天發佈那個 rsync 算法後,想看看數據壓縮的算法,知道一個經典的壓縮算法 Huffman 算法。你應該據說過 David Huffman 和他的經典的壓縮算法—— Huffman Code,這是一種經過字符出現頻率,Priority Queue,和二叉樹來進行的一種壓縮算法,這種二叉樹又叫 Huffman 二叉樹 —— 一種帶權重的樹。可是網上查了一下,中文社區內好像沒有把這個算法說得很清楚的文章,尤爲是樹的構造,而正好看到一篇國外的文章《A Simple Example of Huffman Code on a String》,其中的例子淺顯易懂,至關不錯,我就轉了過來。注意,我沒有對此文徹底翻譯。算法
咱們直接來看示例,若是咱們須要來壓縮下面的字符串:數組
「beep boop beer!」 oop
首先,咱們先計算出每一個字符出現的次數,咱們獲得下面這樣一張表 :編碼
字符 | 次數 |
‘b’ | 3 |
‘e’ | 4 |
‘p’ | 2 |
‘ ‘ | 2 |
‘o’ | 2 |
‘r’ | 1 |
‘!’ | 1 |
而後,我把把這些東西放到 Priority Queue 中(用出現的次數據當 priority),咱們能夠看到,Priority Queue 是以 Prioirry 排序一個數組,若是 Priority 同樣,會使用出現的次序排序:下面是咱們獲得的 Priority Queue:翻譯
接下來就是咱們的算法——把這個 Priority Queue 轉成二叉樹。咱們始終從 queue 的頭取兩個元素來構造一個二叉樹(第一個元素是左結點,第二個是右結點),並把這兩個元素的 priority 相加,並放回 Priority 中(再次注意,這裏的 Priority 就是字符出現的次數),而後,咱們獲得下面的數據圖表:3d
一樣,咱們再把前兩個取出來,造成一個 Priority 爲2+2=4的結點,而後再放回 Priority Queue 中 :code
繼續咱們的算法(咱們能夠看到,這是一種自底向上的建樹的過程):blog
最終咱們會獲得下面這樣一棵二叉樹:排序
此時,咱們把這個樹的左支編碼爲0,右支編碼爲1,這樣咱們就能夠遍歷這棵樹獲得字符的編碼,好比:‘b’的編碼是 00,’p'的編碼是 101, ‘r’的編碼是 1000。咱們能夠看到出現頻率越多的會越在上層,編碼也越短,出現頻率越少的就越在下層,編碼也越長。ip
最終咱們能夠獲得下面這張編碼表:
字符 | 編碼 |
‘b’ | 00 |
‘e’ | 11 |
‘p’ | 101 |
‘ ‘ | 011 |
‘o’ | 010 |
‘r’ | 1000 |
‘!’ | 1001 |
這裏須要注意一點,當咱們 encode 的時候,咱們是按「bit」來 encode,decode 也是經過 bit 來完成,好比,若是咱們有這樣的 bitset 「1011110111″ 那麼其解碼後就是 「pepe」。因此,咱們須要經過這個二叉樹創建咱們 Huffman 編碼和解碼的字典表。
這裏須要注意的一點是,咱們的 Huffman 對各個字符的編碼是不會衝突的,也就是說,不會存在某一個編碼是另外一個編碼的前綴,否則的話就會大問題了。由於 encode 後的編碼是沒有分隔符的。
因而,對於咱們的原始字符串 beep boop beer!
其對就能的二進制爲 : 0110 0010 0110 0101 0110 0101 0111 0000 0010 0000 0110 0010 0110 1111 0110 1111 0111 0000 0010 0000 0110 0010 0110 0101 0110 0101 0111 0010 0010 0001
咱們的 Huffman 的編碼爲: 0011 1110 1011 0001 0010 1010 1100 1111 1000 1001
從上面的例子中,咱們能夠看到被壓縮的比例仍是很可觀的。
做者給出了源碼你能夠看看( C99 標準)
還有個特色:哈夫曼樹的同一層的節點位置能夠互換,可是最終得出來的編碼只有惟一前綴的