資料結構之哈夫曼樹
一、哈夫曼樹的基本概念
也叫最優二叉樹
1.引子:
將很多學生的百分制成績轉化為五分製成績
<60: D 60-69: D 70-79:C 80-89:B 90-100:A
兩種判斷的方式所需要的比較總的次數明顯不同,在資料量很大的情況下所需要的時間也會差距很大
2.基本術語
路徑:從樹中的一個結點到另一個結點之間的分支構成這兩個結點間的路徑
節點的路徑長度:兩結點間路徑上的分支數
樹的路徑的長度:從樹根到每一個結點的路徑長度之和
(在結點數目相同的二叉樹中,完全二叉樹是路徑長度最短的二叉樹,但是長度最短的不僅僅是完全二叉樹)
權:將樹中結點賦給一個有著某種含義的數值,則這個數值稱為該結點的權
結點的帶權路徑長度:從根結點到該結點之間的路徑長度與該結點的權的乘積
樹的帶權路徑長度:樹中跟結點到所有葉子節點的帶權路徑長度之和,記作:WPL
哈夫曼樹就是帶權路徑長度(WPL)最短的樹
注:帶權路徑長度最短是在‘度相同’的樹中比較而得到的結果。要麼都是二叉樹,或者三叉樹等等
二、哈夫曼樹的結構演算法
1.演算法思路
貪心演算法:構造哈夫曼樹時首先選擇權值最小的葉子節點構造。
1.根據給定的n個權值{w1,w2,w3...,wn}構成n棵二叉樹森林F={T1,T2,T3....Tn},其中每一個二叉樹只有一個帶權為w的根節點(每一個二叉樹只有一個節點)
2.從F中選取兩個根結點的權值最小的樹作為左右子樹,構造一棵新的二叉樹,且設定新的二叉樹的根結點的權值作為其左右子樹上根節點的權值之和
3.在F中刪除這兩棵樹,同時將新得到的二叉樹加入森林中
4.重複(2)和(3),直到森林只有一棵樹為止,這棵樹即為哈夫曼樹
總結:1.在哈夫曼演算法中,初始時有n棵二叉樹,要經過n-1次合併最終形成哈夫曼樹。
2.經過n-1次合併產生n-1個新結點,且這n-1個新結點都是具有兩個孩子的分支節點。一共會產生2n-1個結點。
2.演算法實現
採用順序儲存結構——一維結構陣列
設定從0到2n-1個結點
void CreatHuffmanTree (HuffmanTree HT,int n) { // 構造哈夫曼樹 if(n<=1) return m = 2*n-1 //陣列共有2n-1個元素 HT = new HTNode[m+1] //0號單元未用,HT[m]表示根節 for(i=1;i<=m;++i) { // 將2n-1個元素的lch、rch、parent 置為0 HT[i].lch = 0; HT[i].rch = 0; HT[i].parent = 0 } for(i=1;i<=n;++i) cin>>HT[i].weight // 輸入前n個元素weight值 // 初始化結束,下面開始建立哈夫曼樹 for(i=n+1;i<=m;i++) { Select(HT,i+1,s1,s2) // 選擇parent為0且為最小的兩個節點並且返回他們在HT中的需要s1和s2; HT[s1].parent=i; HT[s2].parent=i // 將最小的結點的雙親負責為新結點,表示從F中刪除s1和s2 HT[i].lch=s1; HT[i].rch=s2 // s1和s2分別作為i的左右孩子 HT[i].weight=HT[s1].weight+HT[s2].weight // i的權值為左右孩子權值之和 } }
三、哈夫曼樹應用
1.哈夫曼編碼(最優的字首碼)
1.統計字符集中每個字元在電文中出現的平均概率(概率越大要求編碼越短)
2.利用哈夫曼編碼:權越大的葉子離根越近;將每個字元概率作為權值,構造哈夫曼樹。則概率越大的結點,路徑越短
3.在哈夫曼樹的每個分支上標上0或1;
把結點的左分支標0,右分支標1
把從根到每個葉子的路徑上的標號連線起來,作為該葉子代表的字元編碼
因為每一個字母都是位於二叉樹的葉子節點,因此每一個路徑都具有唯一性,不會產生重碼(一段程式碼可能會產生多個解析結構)。因此哈夫曼編碼是最優的字首碼
2.哈夫曼編碼的演算法實現
從葉子結點向根結點查詢,判斷是左結點還是右結點進行1或者0的編寫,直到到達根結點停止。
void CreatHuffmanCode(Huffmantree HT,HuffmanCode &HC, int n) {
// 從葉子結點到逆向求每個字元的哈夫曼編碼,儲存在編碼表HC中
HC = new char *[n+1] // 分配n個字元編碼的頭指標向量
cd = new char [n] // 分配臨時存放編碼的動態陣列空間
cd[n-1] = '\0' // 編碼結束符
for(i=1;i<=n;++i) { // 逐個字元求哈夫曼編碼
start = n-1; c=i ;;f=HT[i].parent
while(f!=0) { // 從葉子節點開始向上回溯,直到根節點
--start // 回溯一次start向前指一個位置
if(HT[f].lchild == c) { cd[start] = '0'} // 結點c是f的左孩子,則生成程式碼0
else cd[start] = '1' // 結點c是f的右孩子,則生成程式碼1
c = f , f = HT[f].parent // 繼續想上回溯
} // 求出第i個字元的編碼
HC[i] = new char[n-start] // 為第i個字串編碼分配空間
strcpy(HC[i], &cd[start]) // 將求的的編碼從臨時空間cd複製到HC的當前行中
}
delete cd // 釋放臨時空間
}// CreatHuffanCode
3.哈夫曼編碼的解碼演算法
和編碼類似,通過接收字元頻率來構建哈夫曼樹,根據接收到的哈夫曼編碼,用哈夫曼樹從根節點開始遍歷,求出唯一確定的對於字元