什么是哈夫曼樹(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
目標:將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值是一樣的
-哈夫曼編碼
給定一段字符串,如何對字符串進行編碼,可以使得該字符串的編碼存儲空間最少?
假設(shè)有一段文本 包含58個字符,并且由以下7個字符構(gòu)成:a,e,i,s,t,空格(sp),換行(nl);這7個字符出現(xiàn)的次數(shù)不同。如何對這7個字符進行編碼 使得總編碼空間最少?
分析:
(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):任何字符的編碼都不是另一字符編碼的前綴,可以無二義地解碼
用二叉樹進行編碼:(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é)點的下標,根結(jié)點的Parent用-1表示;
數(shù)組中每個元素的類型描述為:
typedef struct {
ElementType Data ;
int Parent ; // 每個結(jié)點的父結(jié)點的下標
} 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中的下標
}
(2)集合的并運算
分別找到X1和X2兩個元素所在集合樹的根結(jié)點,如果它們不同根 則將其中一個根結(jié)點的父結(jié)點指針設(shè)置成另一個根結(jié)點的數(shù)組下標。
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ù)效率會變差,所以盡量把小的集合并到大的集合中去;用負數(shù)代表根結(jié)點,負數(shù)的絕對值為這棵樹的元素個數(shù)