赫夫曼樹及其應用


美國數學家赫夫曼(David Huffman)1952年發明了一種壓縮編碼方法,並得到廣泛應用。為了紀念他的成就,人們把他在編碼中用到的特殊的二叉樹叫做赫夫曼樹,他的編碼方法叫做赫夫曼編碼

下面一段程序用來給學生考試成績划分等級:

if (a < 60)
  b = "不及格";
else if (a < 70)
  b = "及格";
else if (a < 80)
  b = "中等";
else if (a < 90)
  b = "良好";
else
  b = "優秀";

這段程序的判斷過程如圖:

圖T36

不過這樣的判斷算法效率可能有問題,因為對於一般的考卷,學生成績在5個等級上的分布規律如下表:

分數

0 ~ 59

60 ~ 69

70 ~ 79

80 ~ 89

90 ~ 100

所占比例

5%

15%

40%

30%

10%

仔細觀察,中等成績(70 ~ 79)比例最高,其次是良好(80 ~ 89),不及格所占比例最少。於是把圖T36中的表示判斷過程的二叉樹重新調整如下圖:

 圖T37

看起來判斷效率肯定是提高了,但具體提高多少未知。下面就來看看赫夫曼先生是如何說的。

赫夫曼樹的定義與原理

首先把上面兩顆二叉樹簡化為葉子結點帶權的二叉樹(注:樹結點之間的邊相關的數叫做權(weight))。其中A表示不及格,B表示及格,C表示中等,D表示良好,E表示優秀。

圖T38

從樹中一個結點到另一個結點之間的分支構成兩個結點之間的路徑,路徑上的分支數目稱作路徑長度。如上圖中左邊的二叉樹,根節點到D的路徑長度為4,而右邊的二叉樹根節點到D的路徑長度為2。樹的路徑長度就是從根節點到每一結點的路徑長度之和。上圖中左邊的二叉樹的路徑長度為1+1+2+2+3+3+4+4=20,右邊的二叉樹的路徑長度為1+2+2+3+3+1+2+2=16。

如果考慮到帶權的結點,結點的帶權路徑長度就是從該結點到根節點之間的路徑長度與結點上權的乘積。樹的帶權路徑長度就是樹中所有葉子結點的帶權路徑長度之和。假設有n個權值{w1,w2,…wn},構造一棵有n個葉子結點的二叉樹,每個葉子結點帶權wk,每個葉子的路徑長度為lk,則其中帶權路徑長度WPL最小的二叉樹稱作赫夫曼樹最優二叉樹。如圖T38中左邊二叉樹的帶權路徑長度為WPL=5*1 + 15*2 + 40*3 + 30*4 + 10*4 = 315,右邊二叉樹的WPL=5*3 + 15*3 + 40*2 + 30*2 + 10*2 = 220。這樣就可以看出右邊的二叉樹的性能要比左邊的二叉樹的性能高上很多。那右邊的二叉樹是否是最優的赫夫曼樹呢?赫夫曼樹是如何構造出來的呢?看看下面的解決辦法:

1 先把有權值的葉子結點按照從小到大的順序排列:A5,E10,B15,D30,C40。

2 取頭兩個最小權值的結點作為一個新結點N1的兩個孩子,相對較小的是左孩子。新結點的權值為兩個葉子權值的和。如下圖:

 圖T39

3 將N1替換A和E,新序列為:N115,B15,D30,C40。

4 重復步驟2,將N1與B作為新結點N2的兩個孩子,N2的權值為15+15=30。如下圖:

 圖T40

5 將N2替換N1和B,新序列為:N230,D30,C40。

6 重復步驟2。將N2和D作為新結點N3的兩個孩子,N3的權值為30+30=60,如下圖:

圖T41

7 將N3替換N2和D,新序列為:C40,N360。

8 重復步驟2,將C與N3作為新結點T的兩個孩子,T是根節點,至此完成赫夫曼樹的構造。如下圖:

圖T42

圖T42中的二叉樹的WPL = 40*1 + 30*2 + 15*3 + 10*4 + 5*4 = 205。經過上面步驟構造出來的二叉樹就是最優的赫夫曼樹。

由此得出赫夫曼樹的構造方法描述:

1 根據給定的n個權值{w1,w2,…wn}構成n棵二叉樹的集合F={T1,T2,…Tn},其中每棵二叉樹Ti中只有一個帶權為wi的根節點,其左右子樹為空。

2 在F中選取兩棵根節點的權值最小的樹作為左右子樹構造一棵新的二叉樹,且新置的二叉樹的根節點的權值為其左右子樹根節點的權值之和。

3 在F中刪除這兩棵樹,同時將新得到的二叉樹加入F中。

4 重復2和3步驟,直到F只含一棵樹為止,這棵樹就是赫夫曼樹。

赫夫曼編碼

赫夫曼在研究這種最優二叉樹時的主要目的是解決當年遠距離通信(主要是電報)的數據傳輸的最優化問題。比如傳輸一串字符“BADCADFEED”,采用二進制數據表示,如下表:

字母

A

B

C

D

E

F

二進制字符

000

001

010

011

100

101

編碼之后的二進制數據流為“001000011010000011101100100011”,對方接收時同樣按照3位一組解碼。現在假設這6個字母出現的頻率不同,A 27%,B %8,C 15%,D 15%,E 30%,F 5%。下面將27、8、15、15、30、5分別作為A、B、C、D、E、F的權值構造赫夫曼樹,如下圖:

 圖T43

將圖T43中赫夫曼樹的權值左分支改為0,右分支改為1,如下圖:

 

圖T44

現在將這6個字母用從根節點到葉子所經過路徑的0或1來編碼,得到的編碼表如下:

字母

A

B

C

D

E

F

二進制字符

01

1001

101

00

11

1000

將“BADCADFEED”再次編碼得到“1001010010101001000111100”,共25個字符,與之前編碼得到的30個字符相比大約節約了17%的存儲和傳輸成本。

在解碼時,用同樣的赫夫曼樹,即發送方和接收方約定好同樣的赫夫曼編碼規則。當接收方接收到“1001010010101001000111100”時,比對圖T44中的赫夫曼樹,由1001正好走到字母B,如下圖:

圖T45

然后是01,則從根結點走到字母A,如下圖:

 圖T46

其余的字母也可相應成功解碼。

仔細觀察上面的赫夫曼編碼表中各個字母的編碼會發現,不存在容易與1001、1000混淆的10、100等編碼。這就說明若要設計長短不等的編碼,則必須是任一字符的編碼都不是另一個字符的編碼的前綴,這種編碼稱作前綴編碼

下面是赫夫曼編碼的定義:

一般的,設需要編碼的字符集為{d1,d2,…,dn},各個字符在電文中出現的次數或頻率集合為{w1,w2,…,wn},以d1,d2,…dn作為葉子結點,以w1,w2,…wn作為相應葉子結點的權值來構造一棵赫夫曼樹。規定赫夫曼樹的左分支代表0,右分支代表1,則從根節點到葉子節點所經過的路徑分支組成的0和1的序列便為該結點對應字符的編碼,這就是赫夫曼編碼

Reference:

[1] 《大話數據結構》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM