最優二叉樹——哈夫曼樹
一:什麼是最優二叉樹?
從我個人理解來說,最優二叉樹就是從已給出的目標帶權結點(單獨的結點) 經過一種方式的組合形成一棵樹.使樹的權值最小. 最優二叉樹是帶權路徑長度最短的二叉樹。根據結點的個數,權值的不同,最優二叉樹的形狀也各不相同。它們的共同點是:帶權值的結點都是葉子結點。權值越小的結點,其到根結點的路徑越長
官方定義:
在權為wl,w2,…,wn的n個葉子所構成的所有二叉樹中,帶權路徑長度最小(即代價最小)的二叉樹稱為最優二叉樹或哈夫曼樹。
二:下面先弄清幾個幾個概念:
1.路徑長度
在樹中從一個結點到另一個結點所經歷的分支構成了這兩個結點間的路徑上的分支數稱為它的路徑長度
2.樹的路徑長度
樹的路徑長度是從樹根到樹中每一結點的路徑長度之和。在結點數目相同的二叉樹中,完全二叉樹的路徑長度最短。
3.樹的帶權路徑長度(Weighted Path Length of Tree,簡記為WPL)
結點的權:在一些應用中,賦予樹中結點的一個有某種意義的實數。
結點的帶權路徑長度:結點到樹根之間的路徑長度與該結點上權的乘積。
樹的帶權路徑長度(Weighted Path Length of Tree):定義為樹中所有葉結點的帶權路徑長度之和,通常記為:
其中:
n表示葉子結點的數目
wi和li分別表示葉結點ki的權值和根到結點
三:用一個例子來理解一下以上概念
【例】給定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)根據給定的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的分支結點。
五:最優二叉樹演算法具體實現思路
在構造哈夫曼樹時,可以設定一個結構陣列HuffNode儲存哈夫曼樹中各結點的資訊,根據二叉樹的性質可知,具有n個葉子結點的哈夫曼樹共有2n-1個結點,所以陣列HuffNode的大小設定為2n-1,陣列元素的結構形式如下:
weight |
lchild |
rchild |
parent |
其中,weight域儲存結點的權值,lchild和rchild域分別儲存該結點的左、右孩子結點在陣列HuffNode中的序號,從而建立起結 點之間的關係。為了判定一個結點是否已加入到要建立的哈夫曼樹中,可通過parent域的值來確定。初始時parent的值為-1,當結點加入到樹中時, 該結點parent的值為其雙親結點在陣列HuffNode中的序號,就不會是-1了。構造哈夫曼樹時,首先將由n個字元形成的n個葉結點存放到陣列HuffNode的前n個分量中,然後根據前面介紹的哈夫曼方法的基本思想,不斷將兩個小子樹合併為一個較大的子樹,每次構成的新子樹的根結點順序放到HuffNode陣列中的前n個分量的後面。
具體實現:
1)儲存結構
#define n 100 //葉結點數目
#define m 2*n-1 //樹中結點總數
typedef struct
{
floatweight; //權值,設權值均大於零
intlchild,rchild,parent; //左右孩子及雙親指標
} HTNode;
typedef HTNode HuffmanTree[m]; //哈夫曼樹是一維陣列
(2)赫夫曼演算法的陣列法構造
void CreateHuffmanTree(HuffmanTree T)
{
int i,p1,p2; //構造哈夫曼樹,T[m-1]為其根結點
InitHuffmanTree(T); //T初始化
InputWeight(T); //輸入葉子權值至T[0..n-1]的weight域
for(i=n;i<m;i++)
{
SelectMin(T,i-1,&p1,&p2);//共進行n-1次合併,新結點依次存於T[i]中
//在T[0…i-1]中選擇兩個權最小的根結點,其序號分別為p1和p2
T[p1].parent=T[p2].parent=i;
T[i].1child=p1; //最小權的根結點是新結點的左孩子
T[i].rchild=p2; //次小權的根結點是新結點的右孩子
T[i].weight=T[p1].weight+T[p2].weight;
}//for
}//CreateHuffman
C語言實現全部程式碼:
#include "stdio.h"
#include "stdlib.h"
#define m 100
struct ptree //定義二叉樹結點型別
{
int w; //定義結點權值
struct ptree *lchild; //定義左子結點指標
struct ptree *rchild; //定義右子結點指標
};
struct pforest //定義連結串列結點型別
{
struct pforest *link;
struct ptree *root;
};
int WPL=0; //初始化WTL為0
struct ptree *hafm();
void travel();
struct pforest *inforest(struct pforest*f,struct ptree *t);
void travel(struct ptree *head,int n)
{
//為驗證harfm演算法的正確性進行的遍歷
struct ptree *p;
p=head;
if(p!=NULL)
{
if((p->lchild)==NULL && (p->rchild)==NULL) //如果是葉子結點
{
printf("%d ",p->w);
printf("the hops of the node is: %d/n",n);
WPL=WPL+n*(p->w); //計算權值
}//if
travel(p->lchild,n+1);
travel(p->rchild,n+1);
}//if
}//travel
struct ptree *hafm(int n, int w[m])
{
struct pforest *p1,*p2,*f;
struct ptree *ti,*t,*t1,*t2;
int i;
f=(pforest *)malloc(sizeof(pforest));
f->link=NULL;
for(i=1;i<=n;i++) //產生n棵只有根結點的二叉樹
{
ti=(ptree*)malloc(sizeof(ptree));//開闢新的結點空間
ti->w=w[i]; //給結點賦權值
ti->lchild=NULL;
ti->rchild=NULL;
f=inforest(f, ti);
//按權值從小到大的順序將結點從上到下地掛在一顆樹上
}//for
while(((f->link)->link)!=NULL)//至少有二棵二叉樹
{
p1=f->link;
p2=p1->link;
f->link=p2->link; //取出前兩棵樹
t1=p1->root;
t2=p2->root;
free(p1); //釋放p1
free(p2); //釋放p2
t=(ptree *)malloc(sizeof(ptree));//開闢新的結點空間
t->w = (t1->w)+(t2->w); //權相加
t->lchild=t1;
t->rchild=t2; //產生新二叉樹
f=inforest(f,t);
}//while
p1=f->link;
t=p1->root;
free(f);
return(t); //返回t
}
pforest *inforest(struct pforest *f,structptree *t)
{
//按權值從小到大的順序將結點從上到下地掛在一顆樹上
struct pforest *p, *q, *r;
struct ptree *ti;
r=(pforest *)malloc(sizeof(pforest)); //開闢新的結點空間
r->root=t;
q=f;
p=f->link;
while (p!=NULL) //尋找插入位置
{
ti=p->root;
if(t->w > ti->w) //如果t的權值大於ti的權值
{
q=p;
p=p->link; //p向後尋找
}//if
else
p=NULL; //強迫退出迴圈
}//while
r->link=q->link;
q->link=r; //r接在q的後面
return(f); //返回f
}
void InPut(int &n,int w[m])
{
printf("please input the sum ofnode/n"); //提示輸入結點數
scanf("%d",&n); //輸入結點數
printf ("please input weight of everynode/n"); //提示輸入每個結點的權值
for(int i=1;i<=n;i++)
scanf("%d",&w[i]); //輸入每個結點權值
}
int main( )
{
struct ptree *head;
int n,w[m];
InPut(n,w);
head=hafm(n,w);
travel(head,0);
printf("The length of the best path isWPL=%d", WPL);//輸出最佳路徑權值之和
return 1;
}