哈弗曼编码与译码

哈弗曼树

  • 定义:

    假设有m个权值{w1,w2,…wm},可以构造一颗含n个叶子结点的二叉树,每个叶子结点的权重为wi,则其中带权路径长度WPL最小的二叉树称为最优二叉树或哈弗曼树。
  • 特点:

    权值越大的结点离根节点越近。根据这个特点可以构造哈弗曼树。

哈弗曼树的构造算法

  1. 根据给定的n个权值{w1,w2,…wn},构造n棵只有根结点的二叉树。构成森林F。

  2. 在森林F中选取两棵根结点权值最小的树作为左右子树构造一颗新的二叉树,置新二叉树根结点权值为其左右子树根节点权值之和。

  3. 从森林F中删除这两棵树,同时将新得到的二叉树加入森林F中。

  4. 重复(2)和(3),直到F只含一棵树为止,这棵树即是 哈弗曼树

    演示过程:
    在这里插入图片描述
    回到目录

哈弗曼算法的实现

  • 哈弗曼的存储表示

    typedef struct {
    
    	char letter, *code;		//letter字符,code生成的哈弗曼编码
    
    	int weight;				//权重
    
    	int parent, lchild, rchild;
    
    }HTNode, *HuffmanTree;
    
  • 构造哈弗曼树

    /*在HT[1...i]中选择parent为0且权值最小的结点  
    
        返回该结点的下标值  
    
        此函数被Select函数调用 
    */
    int Min(HuffmanTree &HT,int i) {   
        int j;  
        unsigned int k = UINT_iMAX;						//假设各结点的权值不会超过UINT_MAX  
        int flag;  
    
        for(j = 1; j <= i; ++j){  
    
            if(HT[j].weight < k && HT[j].parent == 0){	//用父结点是否为0来判断此结点是否已经被选过  
    
                k = HT[j].weight;  						 //找到最小值 
                flag = j;  
            }  
        }  
    
        HT[flag].parent = 1;							//作个标记,说明已经被选择了。
    
        return flag;  
    }  
    
      
    //在HT[1...i]中选择parent为0且权值最小的两个结点,其序号分别为s1,s2  
    //s1 <= s2 
    void Select(HuffmanTree &HT, int i, int &s1, int &s2) {  
        s1 = Min(HT,i);  
        s2 = Min(HT,i);  
    }  
    
    void CreateHuffmanTree(HuffmanTree &HT, char t[], int w[]){		//t[]为a[]形参,即字符,w[]为b[]形参,即权值 
    
    	int m=2*n-1; 				//总共需要2n-1个节点
    	int i, s1, s2;
    
    	if(n<=1)					//如果只有一个就不用创建
    		return ;
    
    	HT=new HTNode[m+1];			//开辟空间
    
    	for(i=1; i<=n; i++){		//将1-n号单元中的双亲、左孩子、右孩子的下标都初始化为0 		
    	
    		HT[i].parent=0;
    		
    		HT[i].lchild=0;
    	
    		HT[i].rchild=0;
    		
    		HT[i].code='\0';
    	
    		HT[i].letter=t[i-1];
    	
    		HT[i].weight=w[i-1]; 	
    	}
    
    	for(i=n+1; i<=m; i++)	{ 		//初始化	
    
    		HT[i].code='\0';
    
    		HT[i].parent=0;
    
    		HT[i].lchild=0;
    
    		HT[i].rchild=0;
    
    		HT[i].letter=' ';
    
    		HT[i].weight=0;
    
    	}
    
    	cout<<"-------------------------"<<endl;		//初始化结束,以下开始创建哈夫曼树 
    	
    	for(i=n+1; i<=m; i++){
    
    		Select(HT, i-1,s1, s2);						//在n个数中找出权值最小的两个
    		
    		HT[s1].parent=i;
    		HT[s2].parent=i;							//将他们两个的parent节点设置为i;	
    	
    		HT[i].lchild=s1;
    		HT[i].rchild=s2;							//把这两个分别当作左右节点
    		HT[i].weight=HT[s1].weight+HT[s2].weight;	//他们两个的双亲的权值为他们两个的和。 
    	
    	}
    }
    

回到目录

哈弗曼不等长编码

在构造哈弗曼树之后,求哈弗曼编码的思想是:
依次以叶子为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支则生成代码1。

void CreatHuffmanCode(HuffmanTree HT){

	int start, c, f;

	int i;

	char *cd=new char [n]; 		//临时存放编码 
	
	cd[n-1]='\0';

	cout<<"字符编码为:"<<endl;

	for(i=1; i<=n; i++){

		start=n-1;	//开始时指向编码结束符位置 
	
		c=i;	
		f=HT[i].parent;	//f指向节点c的双亲节点 
	
		while(f!=0){	//从叶子节点开始向上回溯,直到根节点 

			--start;

			if(HT[f].lchild==c){	//如果是左孩子,则生成代码‘0 ’ 
				
				cd[start]='0';
			}

			else{			//如果是左孩子,则生成代码‘1 ’ 
				cd[start]='1';
			}

			c=f;

			f=HT[f].parent;	//继续向上回溯 

		}		
	
		HT[i].code=new char[n-start];
	
		strcpy(HT[i].code,&cd[start]);
	
		cout<<HT[i].letter<<":"<<HT[i].code<<endl;

	}
	
	delete cd;
	
}

回到目录

哈弗曼译码

void HuffmanTreeYima(HuffmanTree HT,char cod[],int b) {          //译码 

	char code[100]; 

    char temp[50]; 

    int t=0; 

	int s=0; 

 	int xx=0;									//记录从第几位起不能译码 

    for(int i=0; i<b; i++){ 
	
		temp[t++]=cod[i];    		 			//读取字符,直到找到编码 

	    temp[t] = '\0';       		 			//有效字符串
	
	    for(int j=1;j<=n;j++){       			//依次与所有字符编码开始匹配
		
	  	 	if (!strcmp(HT[j].code,temp)){      //匹配成功   
	
	    			code[s]=HT[j].letter;   	//将字符保存到code中
	
	                s++; 
	
	  				xx+=t;
	
	                strcpy(temp,"");            //将TEMP置空 
	
	                t=0;         				//t置空
	
	                break;
	
	 	  	}	
	  	} 	
	} 

	if(t==0){     //t如果被置空了,表示都匹配出来了,打印译码

	 	code[s]='\0';

	    cout<<"译码为:"<<endl; 

	    cout<<code<<endl;
	}
	else{                //t如果没有被置空 , 源码无法被完全匹配
	  	cout<<"二进制源码有错!从第"<<xx+1<<"位开始"<<endl;
	}

} 

回到目录

总的代码:

#include<iostream>
#include<string.h>
#define  UINT_iMAX 10000 
using namespace std;

typedef struct {

	char letter, *code;		//letter字符,code生成的哈弗曼编码
	
	int weight;				//权重

	int parent, lchild, rchild;

}HTNode, *HuffmanTree;
 

int n;	//全局变量会被默认赋值0 
char coding[100];


/*在HT[1...i]中选择parent为0且权值最小的结点  

    返回该结点的下标值  

    此函数被Select函数调用 
*/
int Min(HuffmanTree &HT,int i) {   

    int j;  

    unsigned int k = UINT_iMAX;	//假设各结点的权值不会超过UINT_MAX  

    int flag;  

    for(j = 1; j <= i; ++j){  

        if(HT[j].weight < k && HT[j].parent == 0){	//用父结点是否为0来判断此结点是否已经被选过  

            k = HT[j].weight;  //找到最小值 
            flag = j;  
        }  
    }  

    HT[flag].parent = 1;	//作个标记,说明已经被选择了。

    return flag;  

}  

  
//在HT[1...i]中选择parent为0且权值最小的两个结点,其序号分别为s1,s2  
//s1 <= s2 
void Select(HuffmanTree &HT, int i, int &s1, int &s2) { 
 
    s1 = Min(HT,i);  
    s2 = Min(HT,i);  
}  

void CreateHuffmanTree(HuffmanTree &HT, char t[], int w[]){		//t[]为a[]形参,即字符,w[]为b[]形参,即权值 

	int m=2*n-1; 	//总共需要2n-1个节点
	int i, s1, s2;

	if(n<=1)	//如果只有一个就不用创建
		return ;

	HT=new HTNode[m+1];	//开辟空间

	for(i=1; i<=n; i++){		//将1-n号单元中的双亲、左孩子、右孩子的下标都初始化为0 		
	
		HT[i].parent=0;
		
		HT[i].lchild=0;
	
		HT[i].rchild=0;
		
		HT[i].code='\0';
	
		HT[i].letter=t[i-1];
	
		HT[i].weight=w[i-1]; 	
	}

	for(i=n+1; i<=m; i++)	{ 		//初始化	

		HT[i].code='\0';

		HT[i].parent=0;

		HT[i].lchild=0;

		HT[i].rchild=0;

		HT[i].letter=' ';

		HT[i].weight=0;

	}

	cout<<"-------------------------"<<endl;		//初始化结束,以下开始创建哈夫曼树 
	
	for(i=n+1; i<=m; i++){

		Select(HT, i-1,s1, s2);//在n个数中找出权值最小的两个
		
		HT[s1].parent=i;
		HT[s2].parent=i;//将他们两个的parent节点设置为i;	
	
		HT[i].lchild=s1;
		HT[i].rchild=s2;//把这两个分别当作左右节点
		HT[i].weight=HT[s1].weight+HT[s2].weight;//他们两个的双亲为他们两个的和。 
	
	}
}

void CreatHuffmanCode(HuffmanTree HT){

	int start, c, f;

	int i;

	char *cd=new char [n]; 		//临时存放编码 
	
	cd[n-1]='\0';

	cout<<"字符编码为:"<<endl;

	for(i=1; i<=n; i++){

		start=n-1;	//开始时指向编码结束符位置 
	
		c=i;	
		f=HT[i].parent;	//f指向节点c的双亲节点 
	
		while(f!=0){	//从叶子节点开始向上回溯,直到根节点 

			--start;

			if(HT[f].lchild==c){	//如果是左孩子,则生成代码‘0 ’ 
				
				cd[start]='0';
			}

			else{			//如果是左孩子,则生成代码‘1 ’ 
				cd[start]='1';
			}

			c=f;

			f=HT[f].parent;	//继续向上回溯 

		}		
	
		HT[i].code=new char[n-start];
	
		strcpy(HT[i].code,&cd[start]);
	
		cout<<HT[i].letter<<":"<<HT[i].code<<endl;

	}
	
	delete cd;
	
}
 
void HuffmanTreeYima(HuffmanTree HT,char cod[],int b) {          //译码 

	char code[100]; 

    char temp[50]; 

    int t=0; 

	int s=0; 

 	int xx=0;									//记录从第几位起不能译码 

    for(int i=0; i<b; i++){ 
	
		temp[t++]=cod[i];    		 			//读取字符,直到找到编码 

	    temp[t] = '\0';       		 			//有效字符串
	
	    for(int j=1;j<=n;j++){       			//依次与所有字符编码开始匹配
		
	  	 	if (!strcmp(HT[j].code,temp)){      //匹配成功   
	
	    			code[s]=HT[j].letter;   	//将字符保存到code中
	
	                s++; 
	
	  				xx+=t;
	
	                strcpy(temp,"");            //将TEMP置空 
	
	                t=0;         				//t置空
	
	                break;
	
	 	  	}	
	  	} 	
	} 

	if(t==0){     //t如果被置空了,表示都匹配出来了,打印译码

	 	code[s]='\0';

	    cout<<"译码为:"<<endl; 

	    cout<<code<<endl;
	}
	else{                //t如果没有被置空 , 源码无法被完全匹配
	  	cout<<"二进制源码有错!从第"<<xx+1<<"位开始"<<endl;
	}

} 

int main(){  

    HuffmanTree HT;   

    char a[20], buff[20], p;	//a存放字符 buff为输入的字符串 p为输入译码时的字符 

    int b[20];//存放权值信息 

    int  i, j;

	int symbol=1, x, k; //译码时做判断用的变量  

    cout<<"请输入一段字符:";

    cin.getline(buff,10);		//接收10个字符到m中,其中最后一个为'\0'.

    int len=strlen(buff); 

 	for (i=0;i<len;i++) {
 		
		for(j=0; j<n;  j++) {

		   if (a[j]==buff[i]) {
		
		   	 	b[j]=b[j]+1;		
		    	break;		
		   }
		
		}

		if (j>=n){

			a[n]=buff[i];

			b[n]=1;

			n++;

		}

 	}

	cout<<"字符和权值信息如下"<<endl;

	for (i=0;i<n;i++){

	  cout<<"字符:"<<a[i]<<"  权值:"<<b[i]<<endl;

	}

    CreateHuffmanTree(HT, a, b);

    CreatHuffmanCode(HT);

    

	cout<<"译码:"<<endl;
	
 	while(1){ 

  		cout<<"请输入要译码的二进制字符串,输入'#'结束:";

   		x=1;		//判断是否有非法字符只能是0 1 

   		k=0;		//作为循环变量来使coding[k]=输入的字符 

   		symbol=1;	//判断是否输入结束 

 		while(symbol){ 

   			cin>>p; 

   			if(p!='1'&&p!='0'&&p!='#'){ //若存在其它字符,x设为0,表示输入的不是二进制
   			 	x=0;
   			}

  			coding[k]=p;

   			if(p=='#')  	symbol=0;  //#号结束标志

  			k++;

  		}

 		if(x==1){
  			HuffmanTreeYima(HT,coding,k-1);        //进行译码
  		}
  		else{
  			 cout<<"有非法字符!"<<endl;
  		}

		cout<<"是否继续?(Y/N):";
		cin>>p;
		
		if(p=='y'||p=='Y')	 continue;		
		else 	break;

 	}
    return 0;  
}  

回到目录

运行结果:

在这里插入图片描述
上面运行结果存储变化:
HT的初态:
在这里插入图片描述
HT的终态:
在这里插入图片描述
回到目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值