数据结构----------哈夫曼树的创建与编码

哈夫曼树

定义:哈夫曼树又称最优树,是一类带权路径最短的树。

结点的带权路径:结点所代表的数乘以从根到改结点所经过的路(即改结点所在的深度-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;
} 

                                                                                         

 

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