複製鏈接
請複製以下鏈接發送給好友

霍夫曼編碼

鎖定
霍夫曼編碼(英語:Huffman Coding),又譯為哈夫曼編碼、赫夫曼編碼,是一種用於無損數據壓縮熵編碼(權編碼)算法。由大衞·霍夫曼在1952年發明。
計算機數據處理中,霍夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。
例如,在英文中,e的出現機率最高,而z的出現概率則最低。當利用霍夫曼編碼對一篇英文進行壓縮時,e極有可能用一個比特來表示,而z則可能花去25個比特(不是26)。用普通的表示方法時,每個英文字母均佔用一個字節,即8個比特。二者相比,e使用了一般編碼的1/8的長度,z則使用了3倍多。倘若我們能實現對於英文中各個字母出現概率的較準確的估算,就可以大幅度提高無損壓縮的比例。
霍夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點的層數)。樹的路徑長度是從樹根到每一結點的路徑長度之和。
中文名
霍夫曼編碼
外文名
Huffman Encoding

霍夫曼編碼發展歷史

1951年,霍夫曼和他在MIT信息論的同學得選擇是完成學期報告還是期末考試。導師羅伯特·法諾出的學期報告題目是,查找最有效的二進制編碼。由於無法證明哪個已有編碼是最有效的,霍夫曼放棄對已有編碼的研究,轉向新的探索,最終發現了基於有序頻率二叉樹編碼的想法,並很快證明了這個方法是最有效的。霍夫曼使用自底向上的方法構建二叉樹,避免了次優算法香農-範諾編碼的最大弊端──自頂向下構建樹。
1952年,於論文《一種構建極小多餘編碼的方法》(A Method for the Construction of Minimum-Redundancy Codes)中發表了這個編碼方法。
Huffman在1952年根據香農(Shannon)在1948年和範若(Fano)在1949年闡述的這種編碼思想提出了一種不定長編碼的方法,也稱霍夫曼(Huffman)編碼。霍夫曼編碼的基本方法是先對圖像數據掃描一遍,計算出各種像素出現的概率,按概率的大小指定不同長度的唯一碼字,由此得到一張該圖像的霍夫曼碼錶。編碼後的圖像數據記錄的是每個像素的碼字,而碼字與實際像素值的對應關係記錄在碼錶中。
霍夫曼編碼是可變字長編碼(VLC)的一種。 Huffman於1952年提出一種編碼方法,該方法完全依據字符出現概率來構造異字頭的平均長度最短的碼字,有時稱之為最佳編碼,一般就稱Huffman編碼。下面引證一個定理,該定理保證了按字符出現概率分配碼長,可使平均碼長最短。
? 定理:在變字長編碼中,如果碼字長度嚴格按照對應符號出現的概率大小逆序排列,則其平 均碼字長度為最小。
? 通過一個實例來説明上述定理的實現過程。設將信源符號按出現的概率大小順序排列為 : ?
U: ( a1 a2 a3 a4 a5 a6 a7 )
0.20 0.19 0.18 0.17 0.15 0.10 0.01
? 給概率最小的兩個符號a6與a7分別指定為“1”與“0”,然後將它們的概率相加再與原來的 a1~a5組合並重新排序成新的原為:
U′: ( a1 a2 a3 a4 a5 a6′ )
0.20 0.19 0.18 0.17 0.15 0.11
? 對a5與a′6分別指定“1”與“0”後,再作概率相加並重新按概率排序得
U″:(0.26 0.20 0.19 0.18 0.17)…
? 直到最後得 U″″:(0.61 0.39)
? 霍夫曼編碼的具體方法:先按出現的概率大小排隊,把兩個最小的概率相加,作為新的概率 和剩餘的概率重新排隊,再把最小的兩個概率相加,再重新排隊,直到最後變成1。每次相 加時都將“0”和“1”賦與相加的兩個概率,讀出時由該符號開始一直走到最後的“1”, 將路線上所遇到的“0”和“1”按最低位到最高位的順序排好,就是該符號的霍夫曼編碼。
例如a7從左至右,由U至U″″,其碼字為0000;
? a6按踐線將所遇到的“0”和“1”按最低位到最高位的順序排好,其碼字為0001…
? 用霍夫曼編碼所得的平均比特率為:Σ碼長×出現概率
? 上例為:0.2×2+0.19×2+0.18×3+0.17×3+0.15×3+0.1×4+0.01×4=2.72 bit
? 可以算出本例的信源熵為2.61bit,二者已經是很接近了。
Huffman方法構造出來的碼並不是唯一的。但對於同一信源而言,其平均碼字長是相同的,編碼效率是一樣的。
Huffman編碼對不同信源的編碼的編碼效率是不同的。只有當信源概率分佈不均勻時,Huffman才會收到顯著效果。

霍夫曼編碼定義解法

霍夫曼編碼廣義

  • 給定
  • 一組符號(Symbol)和其對應的權重值(weight),其權重通常表示成概率(%)。
  • 欲知
  • 一組二元的前置碼,其二元碼的長度為最短。

霍夫曼編碼狹義

  • 輸入
  • 符號集合{\displaystyle S=\left\{s_{1},s_{2},\cdots ,s_{n}\right\}},其S集合的大小為{\displaystyle n}。
  • 權重集合{\displaystyle W=\left\{w_{1},w_{2},\cdots ,w_{n}\right\}},其W集合不為負數且{\displaystyle w_{i}=\mathrm {weight} \left(s_{i}\right),1\leq i\leq n}。
  • 輸出
  • 一組編碼{\displaystyle C\left(S,W\right)=\left\{c_{1},c_{2},\cdots ,c_{n}\right\}},其C集合是一組二進制編碼且{\displaystyle c_{i}}為{\displaystyle s_{i}}相對應的編碼,{\displaystyle 1\leq i\leq n}。
  • 目標
  • 設{\displaystyle L\left(C\right)=\sum _{i=1}^{n}{w_{i}\times \mathrm {length} \left(c_{i}\right)}}為{\displaystyle C}的加權的路徑長,對所有編碼{\displaystyle T\left(S,W\right)},則{\displaystyle L\left(C\right)\leq L\left(T\right)}

霍夫曼編碼示例

霍夫曼樹常處理符號編寫工作。根據整組數據中符號出現的頻率高低,決定如何給符號編碼。如果符號出現的頻率越高,則給符號的碼越短,相反符號的號碼越長。假設我們要給一個英文單字"F O R G E T"進行霍夫曼編碼,而每個英文字母出現的頻率分別列在Fig.1
演算過程[編輯]
(一)進行霍夫曼編碼前,我們先創建一個霍夫曼樹。
  • ⒈將每個英文字母依照出現頻率由小排到大,最小在左,如Fig.1
  • ⒉每個字母都代表一個終端節點(葉節點),比較F.O.R.G.E.T六個字母中每個字母的出現頻率,將最小的兩個字母頻率相加合成一個新的節點。如Fig.2所示,發現FO的頻率最小,故相加2+3=5。
  • ⒊比較5.R.G.E.T,發現RG的頻率最小,故相加4+4=8。
  • ⒋比較5.8.E.T,發現5E的頻率最小,故相加5+5=10。
  • ⒌比較8.10.T,發現8T的頻率最小,故相加8+7=15。
  • ⒍最後剩10.15,沒有可以比較的對象,相加10+15=25。
最後產生的樹狀圖就是霍夫曼樹,參考Fig.2
(二)進行編碼
  • 1.給霍夫曼樹的所有左鏈接'0'與右鏈接'1'。
  • 2.從樹根至樹葉依序記錄所有字母的編碼,如Fig.3

霍夫曼編碼實現方法

霍夫曼編碼數據壓縮

實現霍夫曼編碼的方式主要是創建一個二叉樹和其節點。這些樹的節點可以存儲在數組裏,數組的大小為符號(symbols)數的大小n,而節點分別是終端節點(葉節點)與非終端節點(內部節點)。
一開始,所有的節點都是終端節點,節點內有三個字段:
1.符號(Symbol)
2.權重(Weight、Probabilities、Frequency)
3.指向父節點的鏈接(Link to its parent node)
而非終端節點內有四個字段:
1.權重(Weight、Probabilities、Frequency)
2.指向兩個子節點的鏈接(Links to two child node)
3.指向父節點的鏈接(Link to its parent node)
基本上,我們用'0'與'1'分別代表指向左子節點與右子節點,最後為完成的二叉樹共有n個終端節點與n-1個非終端節點,去除了不必要的符號併產生最佳的編碼長度。
過程中,每個終端節點都包含着一個權重(Weight、Probabilities、Frequency),兩兩終端節點結合會產生一個新節點,新節點的權重是由兩個權重最小的終端節點權重之總和,並持續進行此過程直到只剩下一個節點為止。
實現霍夫曼樹的方式有很多種,可以使用優先隊列(Priority Queue)簡單達成這個過程,給與權重較低的符號較高的優先級(Priority),算法如下:
⒈把n個終端節點加入優先隊列,則n個節點都有一個優先權Pi,1 ≤ i ≤ n
⒉如果隊列內的節點數>1,則:
  • ⑴從隊列中移除兩個最小的Pi節點,即連續做兩次remove(min(Pi), Priority_Queue)
  • ⑵產生一個新節點,此節點為(1)之移除節點之父節點,而此節點的權重值為(1)兩節點之權重和
  • ⑶把(2)產生之節點加入優先隊列中
⒊最後在優先隊列裏的點為樹的根節點(root)
而此算法的時間複雜度(Time Complexity)為O(nlogn);因為有n個終端節點,所以樹總共有2n-1個節點,使用優先隊列每個循環須O(logn)。
此外,有一個更快的方式使時間複雜度降至線性時間(Linear Time)O(n),就是使用兩個隊列(Queue)創件霍夫曼樹。第一個隊列用來存儲n個符號(即n個終端節點)的權重,第二個隊列用來存儲兩兩權重的合(即非終端節點)。此法可保證第二個隊列的前端(Front)權重永遠都是最小值,且方法如下:
⒈把n個終端節點加入第一個隊列(依照權重大小排列,最小在前端)
⒉如果隊列內的節點數>1,則:
  • ⑴從隊列前端移除兩個最低權重的節點
  • ⑵將(1)中移除的兩個節點權重相加合成一個新節點
  • ⑶加入第二個隊列
⒊最後在第一個隊列的節點為根節點
雖然使用此方法比使用優先隊列的時間複雜度還低,但是注意此法的第1項,節點必須依照權重大小加入隊列中,如果節點加入順序不按大小,則需要經過排序,則至少花了O(nlogn)的時間複雜度計算。
但是在不同的狀況考量下,時間複雜度並非是最重要的,如果我們今天考慮英文字母的出現頻率,變量n就是英文字母的26個字母,則使用哪一種算法時間複雜度都不會影響很大,因為n不是一筆龐大的數字。 [1] 

霍夫曼編碼數據解壓縮

簡單來説,霍夫曼碼樹的解壓縮就是將得到的前置碼(Prefix Huffman code)轉換回符號,通常藉由樹的追蹤(Traversal),將接收到的比特串(Bits stream)一步一步還原。但是要追蹤樹之前,必須要先重建霍夫曼樹 ;某些情況下,如果每個符號的權重可以被事先預測,那麼霍夫曼樹就可以預先重建,並且存儲並重復使用,否則,發送端必須預先發送霍夫曼樹的相關信息給接收端。
最簡單的方式,就是預先統計各符號的權重並加入至壓縮之比特串,但是此法的運算量花費相當大,並不適合實際的應用。若是使用Canonical encoding,則可精準得知樹重建的數據量只佔B2^B比特(其中B為每個符號的比特數(bits))。如果簡單將接收到的比特串一個比特一個比特的重建,例如:'0'表示父節點,'1'表示終端節點,若每次讀取到1時,下8個比特則會被解讀是終端節點(假設數據為8-bit字母),則霍夫曼樹則可被重建,以此方法,數據量的大小可能為2~320字節不等。雖然還有很多方法可以重建霍夫曼樹,但因為壓縮的數據串包含"traling bits",所以還原時一定要考慮何時停止,不要還原到錯誤的值,如在數據壓縮時時加上每筆數據的長度等。
參考資料
  • 1.    胡兵, 陳光, 謝永樂. 基於變移霍夫曼編碼的SOC測試數據壓縮[J]. 儀器儀表學報, 2005, 26(11):1114-1118.