數據結構----------哈夫曼樹的創建與編碼

哈夫曼樹

定義:哈夫曼樹又稱最優樹,是一類帶權路徑最短的樹。

結點的帶權路徑:結點所代表的數乘以從根到改結點所經過的路(即改結點所在的深度-1)

樹的帶權路徑(WPL):就是指所有結點帶權路徑和的最小值       

那到底什麼叫WPL呢?

舉個栗子:如圖所示的三顆二叉樹,都含有四個葉子結點a,b,c,d,權值分別爲7,5,2,4;那麼它們的WPL 分別爲多少呢?

1                  2          

                                       3               

很明顯     第三顆樹的WPL 的最小   也就是我們所求的哈夫曼樹

那麼哈夫曼樹到底該怎麼畫呢   ?

這裏爲了方便起見,我們先直接把權值代表該葉子結點    給你一組權值 2,4,5,3,該怎麼畫出哈夫曼樹呢?

第一步:這四個葉子初始時呢 是這個樣子滴 (先排個序        

                  

第二步: 我們找出權值最小的兩個結點2和3,然後將他們合併    

             

重複第二步: 如果有重複 隨便選(所以最後的樹可能不一樣但是WPL相等)   那這裏我們選第二個5和4  合併

            

繼續重複     顯然  現在沒得選了    合併

           

這樣  一顆哈夫曼樹就創建成功了  WPL= 2*2+3*2+4*2+5*2=28

當然  如果在第二步合併的是第一個5和4  那麼哈夫曼樹爲

 

    WPL =2*3+3*3+4*2+5*1=28

畫完哈夫曼樹之後  有沒有發現什麼規律?

1)在哈夫曼樹中,權值越大的結點距離根節點就越近。

2)哈夫曼樹是一顆只有度爲1和度爲2的二叉樹,葉子結點爲n個,那麼度爲2的結點有n-1個,整棵樹的結點就有2n-1個。


那麼 前面學習瞭如何畫出哈夫曼樹,但是如何用代碼實現呢?

第一步: 考慮哈夫曼樹的結點是什麼類型的呢?

由哈夫曼樹的特點我們知道結點數一共有2n-1個那麼我們可以用一維數組存儲這些結點,而對於每一個結點來說,分爲四部分,分別存放權值,雙親,左孩子和右孩子;

                  

typedef struct{
	int weight;
	int parent,lchild,rchild;
}htnode,*nuffmantree;

第二步:創建哈夫曼樹

(1) 初始化

      動態申請2n-1個空間,循環2n-1次將雙親,左孩子,右孩子置爲-1;循環n次,將權值賦給結點的weight;

(2)選取與合併

      選出還沒有雙親的兩個權值最小的結點 ,將它們合併

(3)刪除與加入

     將上一步選出了兩個結點的雙親置更新,新結點的左右孩子更新

(4)重複(2)和(3)n次

代碼實現

//構造哈夫曼樹
void  createnuffmantree(nuffmantree &HT,int w[],int n){
	//初始化
	HT=new htnode[2*n-1]; //動態申請2n-1個空間 
	for(int i=0;i<2*n-1;i++){//雙親 ,左右孩子置-1 
		HT[i].parent=HT[i].lchild=HT[i].rchild=-1; 
	}
	for(int i=0;i<n;i++){//初始化前n個的權值 
		HT[i].weight=w[i];
	}
	//選取與合併,刪除與加入 
	for(int k=n;k<2*n-1;k++){
		int s1,s2;
		Select(HT,k,s1,s2);//選取兩個權值最小值的位置
		HT[s1].parent=k; //s1位置對應雙親爲i 
		HT[s2].parent=k; //s2位置對應雙親爲i 
		HT[k].lchild=s1; //更新左孩子權值 
		HT[k].rchild=s2; //更新右孩子權值 
		HT[k].weight=HT[s1].weight+HT[s2].weight; //更新自己的權值 
	} 
}

Select函數   遍歷0~k-1的結點 找出還沒有雙親的權值最小的兩個結點的下標s1,s2

//Select 函數  找出沒有雙親結點的兩個位置 
void Select(nuffmantree HT,int k,int &s1,int &s2){
	int sw1=1e9,sw2=1e9;//初始化兩個最大權值 
	for(int i=0;i<k-1;i++){
		if(HT[i].parent==-1&&HT[i].weight<sw1){//沒有雙親且對應下標權值小於sw1 
			s1=i;//確定下標 
            sw1=min(sw1,HT[i].weight);//更新sw1 
		}
	}
	for(int i=0;i<k;i++){
		if(HT[i].parent==-1&&HT[i].weight<sw2&&HT[i].weight>sw1){//沒有雙親且對應下標權值小於sw2大於sw1 
			s2=i;//確定下標 
            sw2=min(sw2,HT[i].weight);//更新sw2 
		}
	}
} 

再來個栗子   權值  2 4 5 3

初始化       創建後

 


哈夫曼樹也創建出來了,那麼下一步就是編碼了

            本來以爲編碼挺難的  其實寫出來 也不難嘛~

   既然在上一步我們已經畫好了哈夫曼樹,現在只要對哈夫曼樹加一點點東西就好 

        我們把左枝置0,右枝置1(當然也可以倒過來 隨心所欲~)

對於上個例子  我們把 2 4 5 3 分別當做字母 A B C D的權值

結果如圖:

這樣就得到了 A B C D 的編碼  A:00      B:10     C:11      D:01

完整代碼實現:

#include<iostream>
#include<cstring> 
using namespace std;
typedef struct{
	int weight;
	int parent,lchild,rchild;
}htnode,*nuffmantree;
int w[101],n;
//Select 函數  找出沒有雙親結點的兩個位置 
void Select(nuffmantree HT,int k,int &s1,int &s2){
	int sw1=1e9,sw2=1e9;//初始化兩個最大權值 
	for(int i=0;i<k-1;i++){
		if(HT[i].parent==-1&&HT[i].weight<sw1){//沒有雙親且對應下標權值小於sw1 
			s1=i;//確定下標 
            sw1=min(sw1,HT[i].weight);//更新sw1 
		}
	}
	for(int i=0;i<k;i++){
		if(HT[i].parent==-1&&HT[i].weight<sw2&&HT[i].weight>sw1){//沒有雙親且對應下標權值小於sw2大於sw1 
			s2=i;//確定下標 
            sw2=min(sw2,HT[i].weight);//更新sw2 
		}
	}
} 
//構造哈夫曼樹
void  createnuffmantree(nuffmantree &HT,int w[],int n){
	//初始化
	HT=new htnode[2*n-1]; //動態申請2n-1個空間 
	for(int i=0;i<2*n-1;i++){//雙親 ,左右孩子置-1 
		HT[i].parent=HT[i].lchild=HT[i].rchild=-1; 
	}
	for(int i=0;i<n;i++){//初始化前n個的權值 
		HT[i].weight=w[i];
	}
	//選取與合併,刪除與加入 
	for(int k=n;k<2*n-1;k++){
		int s1,s2;
		Select(HT,k,s1,s2);//選取兩個權值最小值的位置
		HT[s1].parent=k; //s1位置對應雙親爲i 
		HT[s2].parent=k; //s2位置對應雙親爲i 
		HT[k].lchild=s1; //更新左孩子權值 
		HT[k].rchild=s2; //更新右孩子權值 
		HT[k].weight=HT[s1].weight+HT[s2].weight; //更新自己的權值 
	} 
}
//哈夫曼編碼 
void creathuffmancode(nuffmantree HT,char ** &HC,int n){
	HC=new char *[n];  //可以將HC理解爲二維的char[][]數組 
	char *cd=new char[n];//cd 爲一維的char[]數組 
	cd[n-1]='\0'; 
	for(int i=0;i<n;i++){
		int start=n-1;
		int c=i;//定位第幾個字符 
		int f=HT[i].parent;//第i個位置的雙親 
		while(f!=-1){//由葉子向雙親遍歷 
			start--;
			if(HT[f].lchild==c) cd[start]='0';//左孩子爲0 
			else cd[start]='1';//右孩子爲1 
			c=f;f=HT[f].parent;//c記錄當前位置,更新f 
		}
		HC[i]=new char [n-start];
		strcpy(HC[i],&cd[start]);//將cd[]內容複製給HC[i] 
	}
	delete cd;
}
int main(){
	nuffmantree HT;
	char **HC;
    cout<<"請輸入字符個數:"; 
	cin>>n;
    char a[256]; 
	cout<<"請分別輸入字符及對應的權值:"<<endl;
	for(int i=0;i<n;i++)
	 cin>>a[i]>>w[i];
	createnuffmantree(HT,w,n);//建樹 
	creathuffmancode(HT,HC,n);//編碼
	for(int i=0;i<n;i++){//輸出 
		cout<<a[i]<<": ";
		for(int j=0;j<strlen(HC[i]);j++)
		 cout<<HC[i][j];
		cout<<endl;
	} 
	return 0;
} 

                                                                                         

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章