什么是哈夫曼樹(Huffman Tree)
eg:將百分制的考試成績轉(zhuǎn)換為五分制的成績
if ( score < 60 ) grade = 1;
else if ( score < 70 ) grade = 2;
else if ( score < 80 ) grade = 3;
else if ( score < 90 ) grade = 4;
else grade = 5;
建立判定樹,如果考慮到學(xué)生成績的分布概率,我們發(fā)現(xiàn)得3分和得4分的同學(xué)占了絕大多數(shù)的比例,直接用上述判別流程建立判定樹 顯然效率不夠高。
修改判定樹:
if ( score < 80 )
{
if ( score < 70 )
if ( score < 60 ) grade = 1 ;
else grade = 2 ;
} else if ( score < 90 ) grade = 4 ;
else grade = 5;
按照這種判別流程建立的判定樹 效率得到顯著提高娄昆。
如何根據(jù)結(jié)點不同的查找頻率 構(gòu)造更有效的搜索樹?
-哈夫曼樹的定義
帶權(quán)路徑長度(WPL):設(shè)二叉樹有n個葉子結(jié)點缝彬,每個葉子結(jié)點帶有權(quán)值(即頻率)Wk萌焰,從根結(jié)點到每個葉子結(jié)點的長度為Lk,則每個葉子結(jié)點的帶權(quán)路徑長度之和為 WPL=W1L1+W2L2+ …… +Wn*Ln
目標(biāo):將WPL降到最低谷浅。最優(yōu)二叉樹或哈夫曼樹就是WPL最小的二叉樹
-哈夫曼樹的構(gòu)造:
每次把權(quán)值最小的兩棵二叉樹合并扒俯,得到的新結(jié)點的權(quán)值為兩棵樹的權(quán)值的和,再由新結(jié)點與剩下的結(jié)點中再挑出兩個權(quán)值最小的樹 再次合并一疯,直到最后所有樹連接在一起
如何選取兩個最小的樹撼玄?利用堆
typedef struct TreeNode *HuffmanTree ;
struct TreeNode {
int Weight ;
HuffmanTree Left, Right ;
}
HuffmanTree Huffman ( MinHeap H )
{
// 假設(shè) H -> Size 個權(quán)值已經(jīng)存在 H -> Element [ ]-> Weight 里
int i ; HuffmanTree T ;
BuildMinHeap ( H ) ; // 將 H -> Element [ ]按權(quán)值調(diào)整為最小堆
for ( i = 1 ; i < H -> Size ; i ++ ) { // 做 H -> Size - 1 次合并
T = malloc ( sizeof ( struct TreeNode ) ) ; // 建立新結(jié)點
// 從最小堆中刪除一個結(jié)點 作為新樹的左子結(jié)點
T -> Left = DeleteMin ( H ) ;
// 從最小堆中刪除一個結(jié)點 作為新樹的右子結(jié)點
T -> Right = DeleteMin ( H ) ;
// 計算新權(quán)值
T -> Weight = T -> Left -> Weight + T -> Right -> Weight ;
Insert ( H , T ) ; // 將新樹插入最小堆
}
T = DeleteMin ( H ) ;
return T ;
}
整體復(fù)雜度為 O( N logN )
-哈夫曼樹的特點:
沒有度為1的結(jié)點
n個葉結(jié)點的哈夫曼樹共有2n-1個結(jié)點: n0:葉結(jié)點總數(shù),n1:只有一個兒子的結(jié)點總數(shù)墩邀,n2:有兩個兒子的結(jié)點總數(shù)掌猛; 可知:n2=n0-1;
哈夫曼樹的任意非葉結(jié)點的左右子樹交換后仍是哈夫曼樹眉睹;
對同一組權(quán)值荔茬,存在不同構(gòu)的兩棵哈夫曼樹,不過WPL值是一樣的
-哈夫曼編碼
給定一段字符串竹海,如何對字符串進(jìn)行編碼兔院,可以使得該字符串的編碼存儲空間最少?
假設(shè)有一段文本 包含58個字符站削,并且由以下7個字符構(gòu)成:a,e孵稽,i许起,s,t菩鲜,空格(sp)园细,換行(nl);這7個字符出現(xiàn)的次數(shù)不同接校。如何對這7個字符進(jìn)行編碼 使得總編碼空間最少猛频?
分析:
(1)用等長ASCII編碼:58 x 8=464位狮崩;
(2)用等長3位編碼:58 x 3=174位;(因為只有7個字符鹿寻,3位編碼可編8個字符)
(3)不等長編碼:出現(xiàn)頻率高的字符用的編碼短些睦柴,出現(xiàn)頻率低的字符則可以編碼長些
不等長編碼會長生二義性,如a=1毡熏,e=0坦敌,s=11,t=10痢法;那么編碼1011就會有多種不同意思狱窘,因為我們不知道各個編碼長度具體是多少。
前綴碼(prefix code):任何字符的編碼都不是另一字符編碼的前綴财搁,可以無二義地解碼
用二叉樹進(jìn)行編碼:(1)左右分支:0蘸炸、1 (2)字符只在葉結(jié)點上
只要待編字符在葉結(jié)點上,其二叉樹編碼都不是另一字符編碼的前綴
由哈夫曼樹構(gòu)造一棵編碼代價最小的樹
例:
-集合的表示:
集合運算:交集尖奔、并集搭儒、補集、差集越锈,判定一個元素是否屬于某一集合
并查集:集合并仗嗦、查某元素屬于什么集合
eg:10臺電腦{1,2甘凭,3稀拐,…,9丹弱,10}已知下列電腦之間已經(jīng)實現(xiàn)了連接:1和2德撬、2和4、3和5躲胳、4和7蜓洪、5和8、6和9坯苹、6和10隆檀,問2和7之間、5和9之間是否是連通的
思路:(1)將10臺電腦看成10個集合(2)已知一種連接“x和y”粹湃,就將x和y對應(yīng)的集合合并(3)查詢“x和y是否是連通的”就是判別x和y是否屬于同一集合恐仑;這種思路就是并查集。
并查集問題中集合存儲的實現(xiàn):
可以用樹結(jié)構(gòu)表示集合为鳄,樹的每一個結(jié)點代表一個集合元素裳仆;雙親表示法:孩子指向雙親
采用數(shù)組存儲形式:data表示元素的值,Parent表示其父結(jié)點的下標(biāo)孤钦,根結(jié)點的Parent用-1表示歧斟;
數(shù)組中每個元素的類型描述為:
typedef struct {
ElementType Data ;
int Parent ; // 每個結(jié)點的父結(jié)點的下標(biāo)
} SetType ;
-集合運算
(1)查找某個元素所在的集合(用根結(jié)點表示)
int Find ( SetType S [ ], ElementType X )
{ // 在數(shù)組 S 中查找值為 X 的元素所屬的集合
// MaxSize 是全局變量纯丸,為數(shù)組 S 的最大長度
int i ;
for ( i = 0 ; i < MaxSize && S [ i ]. Data != X ; i ++ ) ;
if ( i >= MaxSize ) return -1 ; // 未找到X,返回-1
for ( ; S [ i ] . Parent >= 0 ; i = S [ i ] . Parent ) ;
return i ; // 找到X所屬集合静袖,返回樹根結(jié)點在數(shù)組S中的下標(biāo)
}
(2)集合的并運算
分別找到X1和X2兩個元素所在集合樹的根結(jié)點觉鼻,如果它們不同根 則將其中一個根結(jié)點的父結(jié)點指針設(shè)置成另一個根結(jié)點的數(shù)組下標(biāo)。
void Union ( SetType S[ ], ElementType X1 , ElementType X2 )
{
int Root1 , Root2 ;
Root1 = Find ( S , X1 ) ;
Root2 = Find ( S , X2 ) ;
if ( Root != Root2 ) S[ Root2 ] . Parent = Root1 ;
}
這樣并集后勾徽,樹越來越大越來越高滑凉,樹高了之后Find函數(shù)效率會變差,所以盡量把小的集合并到大的集合中去喘帚;用負(fù)數(shù)代表根結(jié)點畅姊,負(fù)數(shù)的絕對值為這棵樹的元素個數(shù)