哈夫曼樹(最優二叉樹)
百度百科:https://baike.baidu.com/item/%E5%93%88%E5%A4%AB%E6%9B%BC%E6%A0%91/2305769?fr=aladdin
一. 目的:
找出存放一串字符所需的最少的二進制編碼
二. 構造方法:
首先統計出每種字符出現的頻率!(也可以是概率)//權值
------------------------------------------------------------------------------------------------
例如:頻率表 A:60, B:45, C:13 D:69 E:14 F:5 G:3
第一步:找出字符中最小的兩個,小的在左邊,大的在右邊,組成二叉樹。在頻率表中刪除此次找到的兩個數,並加入此次最小兩個數的頻率和。
F和G最小,因此如圖,從字符串頻率計數中刪除F與G,並返回G與F的和 8給頻率表
重複第一步:
-------------------------------------------------------------------------------------------------
頻率表 A:60, B:45, C:13 D:69 E:14 FG:8
最小的是 FG:8與C:13,因此如圖,並返回FGC的和:21給頻率表。
---------------------------------------------------------------------------------------------------
重複第一步:
---------------------------------------------------------------------------------------------------
頻率表 A:60 B: 45 D: 69 E: 14 FGC: 21
如圖
-----------------------------------------------------------------------------------------------------
重複第一步
-----------------------------------------------------------------------------------------------------
頻率表 A:60 B: 45 D: 69 FGCE: 35
-----------------------------------------------------------------------------------------------------
重複第一步
-----------------------------------------------------------------------------------------------------
頻率表 A:60 D: 69 FGCEB: 80
-----------------------------------------------------------------------------------------------------
重複第一步
-----------------------------------------------------------------------------------------------------
頻率表 AD:129 FGCEB: 80
添加 0 和 1,規則左0 右1
頻率表 A:60, B:45, C:13 D:69 E:14 F:5 G:3
每個 字符 的 二進制編碼 爲(從根節點 數到對應的葉子節點,路徑上的值拼接起來就是葉子節點字母的應該的編碼)
字符 | 編碼 |
A | 10 |
B | 01 |
C | 0011 |
D | 11 |
E | 000 |
F | 00101 |
G | 00100 |
那麼當我想傳送 ABC時,編碼爲 10 01 0011
思考:
大家觀察 出現得越多的字母,他的編碼越短 ,出現頻率越少的字母,他的編碼越長。
在信息傳輸過程中,如果這個字母越多,那麼我們希望他越瘦小(編碼短)這樣佔用的編碼越少,其實編碼長的字母也是讓頻率比它多的字母把編碼短的位子都佔用後,他纔去佔用當前最短的編碼。至此讓總的編碼長度最短。
且要保證長編碼的不與短編碼的字母衝突:
比如 不能出現 讀碼 讀到 01 還有長編碼的 字母爲011,如果短編碼爲一個長編碼的左起子串,這就是衝突,意思就是說讀到當前位置已經能確定是什麼字母時不能因爲再讀取一位或幾位讓這個編碼能表示另外的字母,
但哈夫曼樹(最優二叉樹)在構造的時候就避免了這個問題。爲什麼能避免呢,因爲哈夫曼樹的它的字母都在葉子節點上,因此不會出現一個字母的編碼爲另一個字母編碼左起子串的情況。
提問:
1.爲什麼要保證長編碼不與短編碼衝突?
衝突情況:如果我們已經確定D,E,F,G 用 01 ,010 ,10,001的2進制編碼來傳輸了。那麼想傳送FED時,我需要傳送 1001001,接收方可以把它解析爲FDG(10 01 001),當然也能解析爲FED(10 010 01),他兩編碼一樣的,這就是編碼衝突,(這裏編碼衝突的原因,也是因爲編碼時,D的編碼是E的編碼的左起子串了)顯然這是不行的,就像我說壓脈帶,你如果是日本人會理解爲 (你懂得),這就是發出同一種語,得出不同的意的情況。所以不能讓一個字母的二進制代表數,爲另一個字母的二進制代表數的子串。但爲什麼實際情況只要求編碼時,一個編碼不是另一編碼的左起子串呢而不是絕對意義上的非子串呢,因爲計算機從數字串的左邊開始讀,如果從右邊讀,那麼可以要求是非右起(無奈)。你又可以問了爲什麼編碼要求是非左起或非右起不直接規定不能是子串呢(也行,不過=>),比如說原文中B就是C,F,G的子串,那這不就不符合規則了麼。這裏是爲了哈夫曼的根本目的,優化編碼位佔用問題,如果完全不能有任何子串那麼編碼將會非常龐大。但這裏計算機是一位一位的·讀取編碼的,只需要保證計算機在讀取中不會誤判就行。並且編碼佔用最少。
code:0110101001110
左起子串:011
右起子串:110
絕對非子串:1110111 此串在code中完全找不到
2.那麼哈夫曼樹怎麼避免左起子串問題呢?
因爲哈夫曼是從葉子節點開始構造,構造到根節點的,而且構造時,都是計算兩個權值的節點的和再與其他葉子節點再生成一個父節點來組成一個新的樹。並且不允許任何帶有權值的節點作爲他們的父節點。這也保證了所有帶有權值的節點都被構造爲了葉子節點。然後最後編碼的時候是從根節點開始走到葉子節點而得出的編碼。在有權值的節點又不會出現在任何一條路的路途中的情況,只會出現在終點的情況下,因此不會出現01代表一個字母011又代表一個字母。
又如原文ABC編碼爲10010011的情況,當計算機讀到10時,由於有左起子串不衝突的原則。那麼計算機完全可以保證當前的10就是A字母,然後再往下讀010011的部分,然後當讀到01時,也完全能確定B,C同理,而不用說因爲會出現衝突的編碼而接着繼續讀取之後的編碼來確定前面的編碼。這樣對信息的判斷和效率是相當的不利的,也不是說不可以。即使你ABCD,分別用01,011,0111,01111來代替也行,傳輸後也能精確識別,但是數據量極大呢,想代替整個中文編碼呢,那0後面得多少個1才能代表完。因此哈夫曼就是爲了獲得最少編碼量代替最多字符串,並且不衝突,系統不會誤判而產生的。
3.這裏要提一下同權不同構
已經有朋友問起這個問題了。這裏要說一下哈夫曼樹的構造並不是唯一的。
考慮如下情況:
有權值分別爲 5,29,7,8,14,23,3,11的情況,可以如下圖一樣構造。
帶權路徑長度:
(5+3+7+8)*4+
(11+14)*3+
(23+29)*2
=271
也可以如下圖構造:
帶權路徑長度:
(3+5)*5+
7*4+
(8+11+14)*3+
(23+29)*2
=271
這兩種不同的方式構造出來的哈夫曼樹,得出的帶權路徑長度相等,那麼選哪顆樹都可以,這就叫同權不同構。
此問題由博主 https://me.csdn.net/weixin_43690959 暱稱:叫我Tim就好了~ 提出,在這裏對他表示感謝。
看懂的朋友留個贊,沒看懂的留言我給你單獨講 _(:з」∠)_