什麼叫作赫夫曼樹呢?咱們先來看一個例子。算法
過去咱們小學、中學通常考試都是用百分制來表示學科成績的。這帶來了一個弊端,就是很容易讓學生、家長,甚至老師本身都以分取人,讓分數表明了一切。有時想一想也對,90分和95分也許就只是一道題目對錯的差距,但卻讓兩個孩子可能受到徹底不一樣的待遇,這並不公平。因而在現在提倡素質教育的背景下,咱們不少的學科,特別是小學的學科成績都改做了優秀、良好、中等、及格和不及格這樣模糊的詞語,再也不通報具體的分數。markdown
不過對於老師來說,他在對試卷評分的時候,顯然不能憑感受給優良或及格不及格等成績,所以通常都仍是按照百分制算出每一個學生的成績後,再根據統一的標準換算得出五級分制的成績。好比下面的代碼就實現了這樣的轉換。網絡
if (a<60)
b="不及格";
else if (a<70)
b="及格";
else if (a<80)
b="中等";
else if (a<90)
b="良好";
else
b="優秀";
複製代碼
圖6-12-2粗略看沒什麼問題,但是一般都認爲,一張好的考卷應該是讓學生成績大部分處於中等或良好的範圍,優秀和不及格都應該較少纔對。而上面這樣的程序,就使得全部的成績都須要先判斷是否及格,再逐級而上獲得結果。輸入量很大的時候,其實算法是有效率問題的。 app
圖6-12-2ide
若是在實際的學習生活中,學生的成績在5個等級上的分佈規律如表6-12-1所示。性能
表6-12-1學習
那麼70分以上大約佔總數80%的成績都須要通過3次以上的判斷才能夠獲得結果,這顯然不合理。優化
有沒有好一些的辦法,仔細觀察發現,中等成績(70~79分之間)比例最高,其次是良好成績,不及格的所佔比例最少。咱們把圖6-12-2這棵二叉樹從新進行分配。改爲如圖6-12-3的作法試試看。ui
圖6-12-3編碼
從圖中感受,應該效率要高一些了,到底高多少呢。這樣的二叉樹又是如何設計出來的呢?咱們來看看赫夫曼大叔是如何說的吧。
咱們先把這兩棵二叉樹簡化成葉子結點帶權的二叉樹,如圖6-12-4所示。其中A表示不及格、B表示及格、C表示中等、D表示良好、E表示優秀。每一個葉子的分支線上的數字就是剛纔咱們提到的五級分制的成績所佔比例數。
圖6-12-4
赫夫曼大叔說,從樹中一個結點到另外一個結點之間的分支構成兩個結點之間的路徑,路徑上的分支數目稱作路徑長度。圖6-12-4的二叉樹a中,根結點到結點D的路徑長度就爲4,二叉樹b中根結點到結點D的路徑長度爲2。樹的路徑長度就是從樹根到每一結點的路徑長度之和。二叉樹a的樹路徑長度就爲1+1+2+2+3+3+4+4=20。二叉樹b的樹路徑長度就爲1+2+3+3+2+1+2+2=16。
若是考慮到帶權的結點,結點的帶權的路徑長度爲從該結點到樹根之間的路徑長度與結點上權的乘積。樹的帶權路徑長度爲樹中全部葉子結點的帶權路徑長度之和。假設有n個權值{w1,w2,…,wn},構造一棵有n個葉子結點的二叉樹,每一個葉子結點帶權wk,每一個葉子的路徑長度爲lk,咱們一般記做,則其中帶權路徑長度WPL最小的二叉樹稱作赫夫曼樹。也有很多書中也稱爲最優二叉樹,我我的以爲爲了記念作出巨大貢獻的科學家,既然用他們的名字命名,就應該要堅持用他們的名字稱呼,哪怕「最優」更能體現這棵樹的品質也應該只做爲別名。
有了赫夫曼對帶權路徑長度的定義,咱們來計算一下圖6-12-4這兩棵樹的WPL值。
二叉樹a的WPL=5×1+15×2+40×3+30×4+10×4=315
注意:這裏5是A結點的權,1是A結點的路徑長度,其餘同理。
二叉樹b的WPL=5×3+15×3+40×2+30×2+10×2=220
這樣的結果意味着什麼呢?若是咱們如今有10000個學生的百分制成績須要計算五級分製成績,用二叉樹a的判斷方法,須要作31500次比較,而二叉樹b的判斷方法,只須要22000次比較,差很少少了三分之一量,在性能上提升不是一點點。
那麼如今的問題就是,圖6-12-4的二叉樹b這樣的樹是如何構造出來的,這樣的二叉樹是否是就是最優的赫夫曼樹呢?別急,赫夫曼大叔給了咱們解決的辦法。
1.先把有權值的葉子結點按照從小到大的順序排列成一個有序序列,即:A5,E10,B15,D30,C40。
2.取頭兩個最小權值的結點做爲一個新節點N1的兩個子結點,注意相對較小的是左孩子,這裏就是A爲N1的左孩子,E爲N1的右孩子,如圖6-12-5所示。新結點的權值爲兩個葉子權值的和5+10=15。
圖6-12-5 3.將N1替換A與E,插入有序序列中,保持從小到大排列。即:N115,B15,D30,C40。
4.重複步驟2。將N1與B做爲一個新節點N2的兩個子結點。如圖6-12-6所示。N2的權值=15+15=30。
圖6-12-6
5.將N2替換N1與B,插入有序序列中,保持從小到大排列。即:N230,D30,C40。
6.重複步驟2。將N2與D做爲一個新節點N3的兩個子結點。如圖6-12-7所示。N3的權值=30+30=60。
7.將N3替換N2與D,插入有序序列中,保持從小到大排列。即:C40,N360。
8.重複步驟2。將C與N3做爲一個新節點T的兩個子結點,如圖6-12-8所示。
因爲T便是根結點,完成赫夫曼樹的構造。
圖6-12-7
圖6-12-8
此時的圖6-12-8二叉樹的帶權路徑長度WPL=40×1+30×2+15×3+10×4+5×4=205。與圖6-12-4的二叉樹b的WPL值220相比,還少了15。顯然此時構造出來的二叉樹纔是最優的赫夫曼樹。 不過現實老是比理想要複雜得多,圖6-12-8雖然是赫夫曼樹,但因爲每次判斷都要兩次比較(如根結點就是a<80 && a>=70,兩次比較才能獲得y或n的結果),因此整體性能上,反而不如圖6-12-3的二叉樹性能高。固然這並非咱們要討論的重點了。
經過剛纔的步驟,咱們能夠得出構造赫夫曼樹的赫夫曼算法描述。
1.根據給定的n個權值{w1,w2,…,wn}構成n棵二叉樹的集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權爲wi根結點,其左右子樹均爲空。
2.在F中選取兩棵根結點的權值最小的樹做爲左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值爲其左右子樹上根結點的權值之和。
3.在F中刪除這兩棵樹,同時將新獲得的二叉樹加入F中。
4.重複2和3步驟,直到F只含一棵樹爲止。這棵樹即是赫夫曼樹。
固然,赫夫曼研究這種最優樹的目的不是爲了咱們能夠轉化一下成績。他的更大目的是爲了解決當年遠距離通訊(主要是電報)的數據傳輸的最優化問題。
好比咱們有一段文字內容爲「BADCADFEED」要網絡傳輸給別人,顯然用二進制的數字(0和1)來表示是很天然的想法。咱們如今這段文字只有六個字母ABCDEF,那麼咱們能夠用相應的二進制數據表示,如表6-12-2所示。
表6-12-2
這樣真正傳輸的數據就是編碼後的「001000011010000011101100100011」,對方接收時能夠按照3位一分來譯碼。若是一篇文章很長,這樣的二進制串也將很是的可怕。並且事實上,不論是英文、中文或是其餘語言,字母或漢字的出現頻率是不相同的,好比英語中的幾個元音字母「a e i o u」,中文中的「的了有在」等漢字都是頻率極高。
假設六個字母的頻率爲A 27,B 8,C 15,D 15,E 30,F 5,合起來正好是100%。那就意味着,咱們徹底能夠從新按照赫夫曼樹來規劃它們。
圖6-12-9左圖爲構造赫夫曼樹的過程的權值顯示。右圖爲將權值左分支改成0,右分支改成1後的赫夫曼樹。
圖6-12-9
此時,咱們對這六個字母用其從樹根到葉子所通過路徑的0或1來編碼,能夠獲得如表6-12-3所示這樣的定義。
表6-12-3
咱們將文字內容爲「BADCADFEED」再次編碼,對比能夠看到結果串變小了。 ■ 原編碼二進制串:001000011010000011101100100011 (共30個字符)
■ 新編碼二進制串:1001010010101001000111100 (共25個字符)
也就是說,咱們的數據被壓縮了,節約了大約17%的存儲或傳輸成本。隨着字符的增長和多字符權重的不一樣,這種壓縮會更加顯出其優點。
當咱們接收到1001010010101001000111100這樣壓縮過的新編碼時,咱們應該如何把它解碼出來呢?
編碼中非0即1,長短不等的話實際上是很容易混淆的,因此若要設計長短不等的編碼,則必須是任一字符的編碼都不是另外一個字符的編碼的前綴,這種編碼稱作前綴編碼。
你仔細觀察就會發現,表6-12-3中的編碼就不存在容易與100一、1000混淆的「10」和「100」編碼。
可僅僅是這樣不足以讓咱們去方便地解碼的,所以在解碼時,仍是要用到赫夫曼樹,即發送方和接收方必需要約定好一樣的赫夫曼編碼規則。
當咱們接收到1001010010101001000111100時,由約定好的赫夫曼樹可知,1001獲得第一個字母是B,接下來01意味着第二個字符是A,如圖6-12-10所示,其他的也相應的能夠獲得,從而成功解碼。
圖6-12-10
通常地,設須要編碼的字符集爲{d 1 ,d 2 ,…,d n },各個字符在電文中出現的次數或頻率集合爲{w 1 ,w 2 ,…,w n },以d 1 ,d 2 ,…,d n 做爲葉子結點,以w 1 ,w 2 ,…,w n 做爲相應葉子結點的權值來構造一棵赫夫曼樹。規定赫夫曼樹的左分支表明0,右分支表明1,則從根結點到葉子結點所通過的路徑分支組成的0和1的序列便爲該結點對應字符的編碼,這就是赫夫曼編碼。