1. 程式人生 > >最有二叉樹 哈夫曼樹

最有二叉樹 哈夫曼樹

解碼 合並 這樣的 define 又是 當前 對數 左右子樹 選擇

最優二叉樹

1.樹的路徑長度  

樹的路徑長度是從樹根到樹中每一結點的路徑長度之和。在結點數目相同的二叉樹中,完全二叉樹的路徑長度最短。


2.樹的帶權路徑長度(Weighted Path Length of Tree,簡記為WPL)   

結點的權:在一些應用中,賦予樹中結點的一個有某種意義的實數。  

 結點的帶權路徑長度:結點到樹根之間的路徑長度與該結點上權的乘積。  

 樹的帶權路徑長度(Weighted Path Length of Tree):定義為樹中所有葉結點的帶權路徑長度之和,通常記為:

其中: n表示葉子結點的數目 wi

和li分別表示葉結點ki的權值和根到結點ki之間的路徑長度。

樹的帶權路徑長度亦稱為樹的代價。

3.最優二叉樹或哈夫曼樹

 在權為wl,w2,…,wn的n個葉子所構成的所有二叉樹中,帶權路徑長度最小(即代價最小)的二叉樹稱為最優二叉樹哈夫曼樹

【例】給定4個葉子結點a,b,c和d,分別帶權7,5,2和4。構造如下圖所示的三棵二叉樹(還有許多棵),它們的帶權路徑長度分別為: (a)WPL=7*2+5*2+2*2+4*2=36 (b)WPL=7*3+5*3+2*1+4*2=46 (c)WPL=7*1+5*2+2*3+4*3=35  

其中(c)樹的WPL最小,可以驗證,它就是哈夫曼樹。

技術分享

註意: ① 葉子上的權值均相同時,完全二叉樹一定是最優二叉樹,否則完全二叉樹不一定是最優二叉樹。

② 最優二叉樹中,權越大的葉子離根越近。

③ 最優二叉樹的形態不唯一,WPL最小

構造最優二叉樹

1.哈夫曼算法  

哈夫曼首先給出了對於給定的葉子數目及其權值構造最優二叉樹的方法,故稱其為哈夫曼算法。其基本思想是:  

 (1)根據給定的n個權值wl,w2,…,wn構成n棵二叉樹的森林F={T1,T2,…,Tn},其中每棵二叉樹Ti中都只有一個權值為wi的根結點,其左右子樹均空。  

 (2)在森林F中選出兩棵根結點權值最小的樹(當這樣的樹不止兩棵樹時,可以從中任選兩棵),將這兩棵樹合並成一棵新樹,為了保證新樹仍是二叉樹,需要增加一個新結點作為新樹的根,並將所選的兩棵樹的根分別作為新根的左右孩子(誰左,誰右無關緊要),將這兩個孩子的權值之和作為新樹根的權值。  

 (3)對新的森林F重復(2),直到森林F中只剩下一棵樹為止。這棵樹便是哈夫曼樹。 用哈夫曼算法構造哈夫曼樹的過程見【動畫演示】。

註意: ① 初始森林中的n棵二叉樹,每棵樹有一個孤立的結點,它們既是根,又是葉子

② n個葉子的哈夫曼樹要經過n-1次合並,產生n-1個新結點。最終求得的哈夫曼樹中共有2n-1個結點。 ③ 哈夫曼樹是嚴格的二叉樹,沒有度數為1的分支結點。

2.哈夫曼樹的存儲結構及哈夫曼算法的實現

(1) 哈夫曼樹的存儲結構 用一個大小為2n-1的向量來存儲哈夫曼樹中的結點,其存儲結構為:

#define n 100 //葉子數目

#define m 2*n-1//樹中結點總數

typedef struct

{ //結點類型 float weight; //權值,不妨設權值均大於零

int lchild,rchild,parent; //左右孩子及雙親指針

}HTNode;

typedef HTNode HuffmanTree[m];

//HuffmanTree是向量類型

註意:  因為C語言數組的下界為0,故用-1表示空指針。樹中某結點的lchild、rchild和parent不等於-1時,它們分別是該結點的左、右孩子和雙親結點在向量中的下標。

 這裏設置parent域有兩個作用:其一是使查找某結點的雙親變得簡單;其二是可通過判定parent的值是否為-1來區分根與非根結點。


(2)哈夫曼算法的簡要描述

 在上述存儲結構上實現的哈夫曼算法可大致描述為(設T的類型為HuffmanTree):  

(1)初始化   將T[0..m-1]中2n-1個結點裏的三個指針均置為空(即置為-1),權值置為0。

 (2)輸人  讀人n個葉子的權值存於向量的前n個分量(即T[0..n-1])中。它們是初始森林中n個孤立的根結點上的權值。

 (3)合並  對森林中的樹共進行n-1次合並,所產生的新結點依次放人向量T的第i個分量中(n≤i≤m-1)。

每次合並分兩步:  ①在當前森林T[0..i-1]的所有結點中,選取權最小和次小的兩個根結點[p1]和T[p2]作為合並對象,這裏0≤p1,p2≤i-1。  

② 將根為T[p1]和T[p2]的兩棵樹作為左右子樹合並為一棵新的樹,新樹的根是新結點T[i]。具體操作:   將T[p1]和T[p2]的parent置為i,   將T[i]的lchild和rchild分別置為p1和p2   新結點T[i]的權值置為T[p1]和T[p2]的權值之和。

註意:   合並後T[pl]和T[p2]在當前森林中已不再是根,因為它們的雙親指針均已指向了T[i],所以下一次合並時不會被選中為合並對象。
哈夫曼算法模擬演示過程【參見動畫模擬】


(3)哈夫曼算法的求精 v

oid CreateHuffmanTree(HuffmanTree T)

{//構造哈夫曼樹,T[m-1]為其根結點

int i,p1,p2;

InitHuffmanTree(T); //將T初始化

InputWeight(T); //輸入葉子權值至T[0..n-1]的weight域

for(i=n;i<m;i++){//共進行n-1次合並,新結點依次存於T[i]中

SelectMin(T,i-1,&p1,&p2); //在T[0..i-1]中選擇兩個權最小的根結點,其序號分別為p1和p2

T[p1].parent=T[p2].parent=i; TIi].1child=p1; //最小權的根結點是新結點的左孩子 T[j].rchild=p2; //次小權的根結點是新結點的右孩子

T[i].weight=T[p1].weight+T[p2].weight;

} // end for

}
上述算法中調用的三個函數【參見練習】。

【例】以7個權值:7,5,1,4,8,10,20為例,執行CreateHuffmanTree求最優二叉樹的過程【參見動畫模擬】

哈夫曼編碼:

1. 編碼和解碼  

數據壓縮過程稱為編碼。即將文件中的每個字符均轉換為一個惟一的二進制位串。  數據解壓過程稱為解碼。即將二進制位串轉換為對應的字符。

根據最優二叉樹構造哈夫曼編碼

 利用哈夫曼樹很容易求出給定字符集及其概率(或頻度)分布的最優前綴碼。哈夫曼編碼正是一種應用廣泛且非常有效的數據壓縮技術。該技術一般可將數據文件壓縮掉20%至90%,其壓縮效率取決於被壓縮文件的特征。

1. 具體做法

(1)用字符ci作為葉子,pi或fi做為葉子ci的權,構造一棵哈夫曼樹,並將樹中左分支和右分支分別標記為0和1;

(2)將從根到葉子的路徑上的標號依次相連,作為該葉子所表示字符的編碼。該編碼即為最優前綴碼(也稱哈夫曼編碼)。


2. 哈夫曼編碼為最優前綴碼

 由哈夫曼樹求得編碼為最優前綴碼的原因:   

① 每個葉子字符ci的碼長恰為從根到該葉子的路徑長度li,平均碼長(或文件總長)又是二叉樹的帶權路徑長度WPL。而哈夫曼樹是WPL最小的二叉樹,因此編碼的平均碼長(或文件總長)亦最小。  

 ② 樹中沒有一片葉子是另一葉子的祖先,每片葉子對應的編碼就不可能是其它葉子編碼的前綴。即上述編碼是二進制的前綴碼。


3. 求哈夫曼編碼的算法

(1)思想方法  

給定字符集的哈夫曼樹生成後,求哈夫曼編碼的具體實現過程是:依次以葉子T[i](0≤i≤n-1)為出發點,向上回溯至根為止。上溯時走左分支則生成代碼0,走右分支則生成代碼1。

註意:  

 ① 由於生成的編碼與要求的編碼反序,將生成的代碼先從後往前依次存放在一個臨時向量中,並設一個指針start指示編碼在該向量中的起始位置(start初始時指示向量的結束位置)。   

② 當某字符編碼完成時,從臨時向量的start處將編碼復制到該字符相應的位串bits中即可。  

 ③ 因為字符集大小為n,故變長編碼的長度不會超過n,加上一個結束符‘\0‘,bits的大小應為n+1。


(2)字符集編碼的存儲結構及其算法描述

typedef struct

{

char ch; //存儲字符

char bits[n+1]; //存放編碼位串

}CodeNode;

typedef CodeNode HuffmanCode[n];

void CharSetHuffmanEncoding(HuffmanTree T,HuffmanCode H)

{//根據哈夫曼樹T求哈夫曼編碼表H

int c,p,i;//c和p分別指示T中孩子和雙親的位置

char cd[n+1]; //臨時存放編碼

int start; //指示編碼在cd中的起始位置

cd[n]=‘\0‘; //編碼結束符

for(i=0,i<n,i++)

{ //依次求葉子T[i]的編碼

H[i].ch=getchar();//讀入葉子T[i]對應的字符

start=n; //編碼起始位置的初值

c=i; //從葉子T[i]開始上溯

while((p=T[c].parent)>=0)

{//直至上溯到T[c]是樹根為止

//若T[c]是T[p]的左孩子,則生成代碼0;否則生成代碼1

cd[--start]=(T[p).1child==C)?‘0‘:‘1‘;

c=p; //繼續上溯

}

strcpy(H[i].bits,&cd[start]); //復制編碼位串

}//endfor

}//CharSetHuffmanEncoding


文件的編碼和解碼

 有了字符集的哈夫曼編碼表之後,對數據文件的編碼過程是:依次讀人文件中的字符c,在哈夫曼編碼表H中找到此字符,若H[i].ch=c,則將字符c轉換為H[i].bits中存放的編碼串。

 對壓縮後的數據文件進行解碼則必須借助於哈夫曼樹T,其過程是:依次讀人文件的二進制碼,從哈夫曼樹的根結點(即T[m-1])出發,若當前讀人0,則走向左孩子,否則走向右孩子。

一旦到達某一葉子T[i]時便譯出相應的字符H[i].ch。然後重新從根出發繼續譯碼,直至文件結束。  

文件的編碼和解碼算法【參見練習】。

最有二叉樹 哈夫曼樹