最有二叉树 哈夫曼树
最優(yōu)二叉樹(shù)
1.樹(shù)的路徑長(zhǎng)度????
樹(shù)的路徑長(zhǎng)度是從樹(shù)根到樹(shù)中每一結(jié)點(diǎn)的路徑長(zhǎng)度之和。在結(jié)點(diǎn)數(shù)目相同的二叉樹(shù)中,完全二叉樹(shù)的路徑長(zhǎng)度最短。
2.樹(shù)的帶權(quán)路徑長(zhǎng)度(Weighted Path Length of Tree,簡(jiǎn)記為WPL)?
結(jié)點(diǎn)的權(quán):在一些應(yīng)用中,賦予樹(shù)中結(jié)點(diǎn)的一個(gè)有某種意義的實(shí)數(shù)。?
結(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)度:結(jié)點(diǎn)到樹(shù)根之間的路徑長(zhǎng)度與該結(jié)點(diǎn)上權(quán)的乘積。?
樹(shù)的帶權(quán)路徑長(zhǎng)度(Weighted Path Length of Tree):定義為樹(shù)中所有葉結(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)度之和,通常記為:??????????????????????
其中:??????????? n表示葉子結(jié)點(diǎn)的數(shù)目???? wi和li分別表示葉結(jié)點(diǎn)ki的權(quán)值和根到結(jié)點(diǎn)ki之間的路徑長(zhǎng)度。
??? 樹(shù)的帶權(quán)路徑長(zhǎng)度亦稱為樹(shù)的代價(jià)。
?
3.最優(yōu)二叉樹(shù)或哈夫曼樹(shù)????
在權(quán)為wl,w2,…,wn的n個(gè)葉子所構(gòu)成的所有二叉樹(shù)中,帶權(quán)路徑長(zhǎng)度最小(即代價(jià)最小)的二叉樹(shù)稱為最優(yōu)二叉樹(shù)或哈夫曼樹(shù)。 ?
【例】給定4個(gè)葉子結(jié)點(diǎn)a,b,c和d,分別帶權(quán)7,5,2和4。構(gòu)造如下圖所示的三棵二叉樹(shù)(還有許多棵),它們的帶權(quán)路徑長(zhǎng)度分別為: ??????? (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)樹(shù)的WPL最小,可以驗(yàn)證,它就是哈夫曼樹(shù)。???????
?
?
??注意:???? ① 葉子上的權(quán)值均相同時(shí),完全二叉樹(shù)一定是最優(yōu)二叉樹(shù),否則完全二叉樹(shù)不一定是最優(yōu)二叉樹(shù)。???
? ② 最優(yōu)二叉樹(shù)中,權(quán)越大的葉子離根越近。 ???
③ 最優(yōu)二叉樹(shù)的形態(tài)不唯一,WPL最小
?
?構(gòu)造最優(yōu)二叉樹(shù)
?1.哈夫曼算法????
哈夫曼首先給出了對(duì)于給定的葉子數(shù)目及其權(quán)值構(gòu)造最優(yōu)二叉樹(shù)的方法,故稱其為哈夫曼算法。其基本思想是:?
?
(1)根據(jù)給定的n個(gè)權(quán)值wl,w2,…,wn構(gòu)成n棵二叉樹(shù)的森林F={T1,T2,…,Tn},其中每棵二叉樹(shù)Ti中都只有一個(gè)權(quán)值為wi的根結(jié)點(diǎn),其左右子樹(shù)均空。?
(2)在森林F中選出兩棵根結(jié)點(diǎn)權(quán)值最小的樹(shù)(當(dāng)這樣的樹(shù)不止兩棵樹(shù)時(shí),可以從中任選兩棵),將這兩棵樹(shù)合并成一棵新樹(shù),為了保證新樹(shù)仍是二叉樹(shù),需要增加一個(gè)新結(jié)點(diǎn)作為新樹(shù)的根,并將所選的兩棵樹(shù)的根分別作為新根的左右孩子(誰(shuí)左,誰(shuí)右無(wú)關(guān)緊要),將這兩個(gè)孩子的權(quán)值之和作為新樹(shù)根的權(quán)值。?
(3)對(duì)新的森林F重復(fù)(2),直到森林F中只剩下一棵樹(shù)為止。這棵樹(shù)便是哈夫曼樹(shù)。??? 用哈夫曼算法構(gòu)造哈夫曼樹(shù)的過(guò)程見(jiàn)【動(dòng)畫(huà)演示】。
??注意:???? ① 初始森林中的n棵二叉樹(shù),每棵樹(shù)有一個(gè)孤立的結(jié)點(diǎn),它們既是根,又是葉子???
? ② n個(gè)葉子的哈夫曼樹(shù)要經(jīng)過(guò)n-1次合并,產(chǎn)生n-1個(gè)新結(jié)點(diǎn)。最終求得的哈夫曼樹(shù)中共有2n-1個(gè)結(jié)點(diǎn)。???? ③ 哈夫曼樹(shù)是嚴(yán)格的二叉樹(shù),沒(méi)有度數(shù)為1的分支結(jié)點(diǎn)。??
?
???2.哈夫曼樹(shù)的存儲(chǔ)結(jié)構(gòu)及哈夫曼算法的實(shí)現(xiàn)
(1) 哈夫曼樹(shù)的存儲(chǔ)結(jié)構(gòu)???? 用一個(gè)大小為2n-1的向量來(lái)存儲(chǔ)哈夫曼樹(shù)中的結(jié)點(diǎn),其存儲(chǔ)結(jié)構(gòu)為:??
#define n 100 //葉子數(shù)目??
#define m 2*n-1//樹(shù)中結(jié)點(diǎn)總數(shù)??
typedef struct
{ //結(jié)點(diǎn)類(lèi)型?????? float weight; //權(quán)值,不妨設(shè)權(quán)值均大于零??????
int lchild,rchild,parent; //左右孩子及雙親指針????
}HTNode;??
typedef HTNode HuffmanTree[m];
//HuffmanTree是向量類(lèi)型
?
??注意:???? 因?yàn)镃語(yǔ)言數(shù)組的下界為0,故用-1表示空指針。樹(shù)中某結(jié)點(diǎn)的lchild、rchild和parent不等于-1時(shí),它們分別是該結(jié)點(diǎn)的左、右孩子和雙親結(jié)點(diǎn)在向量中的下標(biāo)。????
這里設(shè)置parent域有兩個(gè)作用:其一是使查找某結(jié)點(diǎn)的雙親變得簡(jiǎn)單;其二是可通過(guò)判定parent的值是否為-1來(lái)區(qū)分根與非根結(jié)點(diǎn)。
(2)哈夫曼算法的簡(jiǎn)要描述????
在上述存儲(chǔ)結(jié)構(gòu)上實(shí)現(xiàn)的哈夫曼算法可大致描述為(設(shè)T的類(lèi)型為HuffmanTree):?
(1)初始化???? 將T[0..m-1]中2n-1個(gè)結(jié)點(diǎn)里的三個(gè)指針均置為空(即置為-1),權(quán)值置為0。
(2)輸人???? 讀人n個(gè)葉子的權(quán)值存于向量的前n個(gè)分量(即T[0..n-1])中。它們是初始森林中n個(gè)孤立的根結(jié)點(diǎn)上的權(quán)值。
(3)合并???? 對(duì)森林中的樹(shù)共進(jìn)行n-1次合并,所產(chǎn)生的新結(jié)點(diǎn)依次放人向量T的第i個(gè)分量中(n≤i≤m-1)。
每次合并分兩步:???? ①在當(dāng)前森林T[0..i-1]的所有結(jié)點(diǎn)中,選取權(quán)最小和次小的兩個(gè)根結(jié)點(diǎn)[p1]和T[p2]作為合并對(duì)象,這里0≤p1,p2≤i-1。????
② 將根為T(mén)[p1]和T[p2]的兩棵樹(shù)作為左右子樹(shù)合并為一棵新的樹(shù),新樹(shù)的根是新結(jié)點(diǎn)T[i]。具體操作:? 將T[p1]和T[p2]的parent置為i,? 將T[i]的lchild和rchild分別置為p1和p2? 新結(jié)點(diǎn)T[i]的權(quán)值置為T(mén)[p1]和T[p2]的權(quán)值之和。
?
??注意:???? 合并后T[pl]和T[p2]在當(dāng)前森林中已不再是根,因?yàn)樗鼈兊碾p親指針均已指向了T[i],所以下一次合并時(shí)不會(huì)被選中為合并對(duì)象。
哈夫曼算法模擬演示過(guò)程【參見(jiàn)動(dòng)畫(huà)模擬】
?
(3)哈夫曼算法的求精?? v
oid CreateHuffmanTree(HuffmanTree T)???
? {//構(gòu)造哈夫曼樹(shù),T[m-1]為其根結(jié)點(diǎn)????
?? int i,p1,p2;????
?? InitHuffmanTree(T); //將T初始化??????
InputWeight(T); //輸入葉子權(quán)值至T[0..n-1]的weight域?????
? for(i=n;i<m;i++){//共進(jìn)行n-1次合并,新結(jié)點(diǎn)依次存于T[i]中??????????
SelectMin(T,i-1,&p1,&p2);?????????? //在T[0..i-1]中選擇兩個(gè)權(quán)最小的根結(jié)點(diǎn),其序號(hào)分別為p1和p2?????????
? T[p1].parent=T[p2].parent=i;?????????? TIi].1child=p1; //最小權(quán)的根結(jié)點(diǎn)是新結(jié)點(diǎn)的左孩子?????????? T[j].rchild=p2; //次小權(quán)的根結(jié)點(diǎn)是新結(jié)點(diǎn)的右孩子????????
?? T[i].weight=T[p1].weight+T[p2].weight;???????
? } // end for????
}
上述算法中調(diào)用的三個(gè)函數(shù)【參見(jiàn)練習(xí)】。
【例】以7個(gè)權(quán)值:7,5,1,4,8,10,20為例,執(zhí)行CreateHuffmanTree求最優(yōu)二叉樹(shù)的過(guò)程【參見(jiàn)動(dòng)畫(huà)模擬】
?
?
哈夫曼編碼:
1. 編碼和解碼????
數(shù)據(jù)壓縮過(guò)程稱為編碼。即將文件中的每個(gè)字符均轉(zhuǎn)換為一個(gè)惟一的二進(jìn)制位串。???? 數(shù)據(jù)解壓過(guò)程稱為解碼。即將二進(jìn)制位串轉(zhuǎn)換為對(duì)應(yīng)的字符。
?
根據(jù)最優(yōu)二叉樹(shù)構(gòu)造哈夫曼編碼????
利用哈夫曼樹(shù)很容易求出給定字符集及其概率(或頻度)分布的最優(yōu)前綴碼。哈夫曼編碼正是一種應(yīng)用廣泛且非常有效的數(shù)據(jù)壓縮技術(shù)。該技術(shù)一般可將數(shù)據(jù)文件壓縮掉20%至90%,其壓縮效率取決于被壓縮文件的特征。
1. 具體做法
(1)用字符ci作為葉子,pi或fi做為葉子ci的權(quán),構(gòu)造一棵哈夫曼樹(shù),并將樹(shù)中左分支和右分支分別標(biāo)記為0和1;
(2)將從根到葉子的路徑上的標(biāo)號(hào)依次相連,作為該葉子所表示字符的編碼。該編碼即為最優(yōu)前綴碼(也稱哈夫曼編碼)。
2. 哈夫曼編碼為最優(yōu)前綴碼???
? 由哈夫曼樹(shù)求得編碼為最優(yōu)前綴碼的原因:?
① 每個(gè)葉子字符ci的碼長(zhǎng)恰為從根到該葉子的路徑長(zhǎng)度li,平均碼長(zhǎng)(或文件總長(zhǎng))又是二叉樹(shù)的帶權(quán)路徑長(zhǎng)度WPL。而哈夫曼樹(shù)是WPL最小的二叉樹(shù),因此編碼的平均碼長(zhǎng)(或文件總長(zhǎng))亦最小。?
② 樹(shù)中沒(méi)有一片葉子是另一葉子的祖先,每片葉子對(duì)應(yīng)的編碼就不可能是其它葉子編碼的前綴。即上述編碼是二進(jìn)制的前綴碼。
3. 求哈夫曼編碼的算法
(1)思想方法? ???
給定字符集的哈夫曼樹(shù)生成后,求哈夫曼編碼的具體實(shí)現(xiàn)過(guò)程是:依次以葉子T[i](0≤i≤n-1)為出發(fā)點(diǎn),向上回溯至根為止。上溯時(shí)走左分支則生成代碼0,走右分支則生成代碼1。???
?
注意:?
① 由于生成的編碼與要求的編碼反序,將生成的代碼先從后往前依次存放在一個(gè)臨時(shí)向量中,并設(shè)一個(gè)指針start指示編碼在該向量中的起始位置(start初始時(shí)指示向量的結(jié)束位置)。?
② 當(dāng)某字符編碼完成時(shí),從臨時(shí)向量的start處將編碼復(fù)制到該字符相應(yīng)的位串bits中即可。?
③ 因?yàn)樽址笮閚,故變長(zhǎng)編碼的長(zhǎng)度不會(huì)超過(guò)n,加上一個(gè)結(jié)束符'\0',bits的大小應(yīng)為n+1。
(2)字符集編碼的存儲(chǔ)結(jié)構(gòu)及其算法描述??
typedef struct
{?????
? char ch; //存儲(chǔ)字符?????
? char bits[n+1]; //存放編碼位串????
}CodeNode;
? typedef CodeNode HuffmanCode[n];?
? void CharSetHuffmanEncoding(HuffmanTree T,HuffmanCode H)?????
{//根據(jù)哈夫曼樹(shù)T求哈夫曼編碼表H?????
? int c,p,i;//c和p分別指示T中孩子和雙親的位置????
?? char cd[n+1]; //臨時(shí)存放編碼????
?? int start; //指示編碼在cd中的起始位置????
?? cd[n]='\0'; //編碼結(jié)束符?????
for(i=0,i<n,i++)
{ //依次求葉子T[i]的編碼??????
??? H[i].ch=getchar();//讀入葉子T[i]對(duì)應(yīng)的字符?????????
start=n; //編碼起始位置的初值????????
? c=i; //從葉子T[i]開(kāi)始上溯?????
???? while((p=T[c].parent)>=0)
{//直至上溯到T[c]是樹(shù)根為止??????
????????? //若T[c]是T[p]的左孩子,則生成代碼0;否則生成代碼1????????
???? cd[--start]=(T[p).1child==C)?'0':'1';???????????
? c=p; //繼續(xù)上溯????????
??? }????
????? strcpy(H[i].bits,&cd[start]); //復(fù)制編碼位串???????
}//endfor??????
}//CharSetHuffmanEncoding
?
文件的編碼和解碼????
有了字符集的哈夫曼編碼表之后,對(duì)數(shù)據(jù)文件的編碼過(guò)程是:依次讀人文件中的字符c,在哈夫曼編碼表H中找到此字符,若H[i].ch=c,則將字符c轉(zhuǎn)換為H[i].bits中存放的編碼串。????
對(duì)壓縮后的數(shù)據(jù)文件進(jìn)行解碼則必須借助于哈夫曼樹(shù)T,其過(guò)程是:依次讀人文件的二進(jìn)制碼,從哈夫曼樹(shù)的根結(jié)點(diǎn)(即T[m-1])出發(fā),若當(dāng)前讀人0,則走向左孩子,否則走向右孩子。
一旦到達(dá)某一葉子T[i]時(shí)便譯出相應(yīng)的字符H[i].ch。然后重新從根出發(fā)繼續(xù)譯碼,直至文件結(jié)束。????
文件的編碼和解碼算法【參見(jiàn)練習(xí)】。
轉(zhuǎn)載于:https://www.cnblogs.com/java2016/p/7669833.html
總結(jié)
以上是生活随笔為你收集整理的最有二叉树 哈夫曼树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第53天:鼠标事件、event事件对象
- 下一篇: matplotlib 雷达图2