重读数据结构之-- 霍夫曼编码

11.霍夫曼编码
    ①统计一篇文章各个字母出现的频率作为权值,字母作为叶子结点,造成一个霍夫曼树,并把这棵树左分支改为0,右分支改为1.每个字母从根开始的路径01编码作为该字母的编码。
    ②前缀编码:如果要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符编码的前缀。利用霍夫曼树生成的各个字母结点的编码就属于前缀编码。  
    ③霍夫曼编码定义: 设需要编码的字符集为{d1,d2...dn},各个字符在电文中出现的次数或频率定义为集合{w1,w2...wn},以dn作为叶子结点,以wn作为叶子权值构成一颗霍夫曼树 规定该霍夫曼树左分支代表0,右分支代表1,则从根节点所经过的路径分支组成的01序列便是该结点对应字符的编码。
    ④代码实现

/*------------------------霍夫曼编码的实现---------------------------*/
#define N 100
#define M 2*N-1
typedef char *HuffmanCode[2*M]; //存放编码的01序列

typedef struct{//定义霍夫曼树上的结点,和结点组成的树
	int weight;
	int parent;
	int LChild;
	int RChild;
}HTNode,Huffman[M+1];

typedef struct Node{//定义字符所代表的叶子结点
	int weight;
	char c;
	int num;//叶子结点的二进制码的长度
}WNode,WeightNode[N];

/*--------------产生叶子结点的字符和权值-------------*/
void CreateWeight(char ch[],int *s,WeightNode CW,int *p){
	int i,j,k;
	int tag;
	*p=0;
	/*----统计各字符出现的次数,结果放入CW----*/
	for(i=0;ch[i]!='\0';i++){
		tag=1;
		for(j=0;j<i;j++){
			if(ch[j]==ch[i]){//此处代码用来过滤重复,每次都只针对一个新字符进行权值计算,一次计算完毕,下次不再对重复的字符统计次数
				tag=0;
				break;
			}
			if(tag){//确定此时的字符是还没统计过的字符
				CW[++*p].c=ch[i];//记录字符值
				CW[*p].weight=1;//出现了一次,统计一下
				for(k=i+1;ch[k]!='\0';k++){//要是后面还再出现,则权值累加
					if(ch[j]==ch[k]){
						CW[*p].weight++; //权值累加
					}
				}
			}
		}
		//至此,一个字符的统计工作完全结束,进入下一个字符
		*s=i;//字符串的长度
	}
	/*----统计各字符出现的次数,结果放入CW----*/
}

/*--------------产生叶子结点的字符和权值-------------*/


/*------------------创建霍夫曼树---------------------------*/
void CreateHuffmanTree(Huffman ht,WeightNode w,int n){
	int i,j;
	int s1,s2;
	for(i=1;i<=n;i++){//遍历待创建树的所有叶子,对叶子结点进行整体的初始化
		ht[i].weight=w[i].weight;
		ht[i].parent=0;
		ht[i].LChild=0;
		ht[i].RChild=0;
	}
	for(i=n+1;i<=2*n-1;i++){//遍历剩余的非叶子结点,进行初始化
		ht[i].weight=0;
		ht[i].parent=0;
		ht[i].LChild=0;
		ht[i].RChild=0;
	}
	for(i=n+1;i<=2*n-1;i++){

		    for(j=1;j<=i-1;j++){
			    if(!ht[j].parent)
				    break;
		    }
			s1=j;//找到第一个双亲不为0的结点
			for(;j<=i-1;j++){//从第一个双亲不为0结点开始往后找双亲为0的权值最小结点
				if(!ht[j].parent){//如果此时结点没有双亲
					s1=ht[s1].weight>ht[j].weight?j:s1;//权值最小的且没有双亲的结点的角标
				}
			}
				ht[s1].parent=i;//给这个结点指定一个双亲
				ht[i].LChild=s1;//把这个结点作为其双亲i的左孩子				
			
			for(j=1;j<=i-1;j++){
				if(!ht[j].parent)
					break;							
			}
			s2=j;//找到第二个双亲不为0的结点	

			for(;j<=i-1;j++){
				if(!ht[j].parent){
					s2=ht[s2].weight>ht[j].weight?j:s2;//继续找下一个双亲为0的权值最小结点的角标
				}
            }
				ht[s2].parent=i;//给第二个权值最小的结点赋值
				ht[i].RChild=s2;//把这个结点作为其双亲i的右孩子
				ht[i].weight=ht[s1].weight+ht[s2].weight;//新结点的权值是两个孩子的和
			
	
	}
}
/*------------------创建霍夫曼树---------------------------*/


/*-------求树的每一个叶子的编码值--------*/
void CrtHuffmanNodeCode(Huffman ht,HuffmanCode h,WeightNode weight,int m,int n){
	int i,c,p,start;
	char *cd;
	cd=(char*)malloc(n*sizeof(char));//创建字符数组用来存放编码值
	cd[n-1]='\0';//字符串结尾标记
	for(i=1;i<=n;i++){
		start=n-1;//cd字符串每次都从末尾开始,即从叶子出发去寻根,路径值从后开始放
		c=i;   //记录当前要寻根的值
		p=ht[i].parent;//p在n+1到2n-1之间
		while(p){ //寻根开始
			start--;
			if(ht[p].LChild==c){//如果i是其双亲的左孩子
				cd[start]='0';//左树为0
			}else{
				cd[start]='1';//右树为1
			}
			c=p;//下次要寻根的是p了
			p=ht[p].parent;//获取p的双亲,进入下一次的循环
		}
		weight[i].num=n-start;//最终确定i结点编码后的01序列长度值
		h[i]=(char*)malloc((n-start)*sizeof(char));//给这个长度的01序列分配一个数组空间
		strcpy(h[i],&cd[start]);//cd[start]是原来存放编码值数组的第一个元素,也是数组的指针

		//--->存放所有叶子结点的数组weight和存放所有叶子结点序列长度的数组h是一一对应的!!!!
	}
	free(cd);
	system("pause");
}
/*-------求树的某一个叶子的编码值--------*/


/*-----求这篇文章中所有字符的编码结果--------*/
void CrtHuffmanCode(char ch[],HuffmanCode h,HuffmanCode hc,WeightNode weight,int m,int n){
	int i,k;
	for(i=0;i<m;i++){
		for(k=1;k<=n;k++){
			if(ch[i]==weight[k].c){//参阅码表对应找文章的字符
				break;
			}
		}
		hc[i]=(char*)malloc(weight[k].num*sizeof(char));//找到一个,则为某个叶子分配对应它路径长度的字符数组
		strcpy(hc[i],h[k]);//本篇文章中所有字符码表存在hc
	}
}

/*-----求这篇文章中所有字符的编码结果--------*/


/*--------解码---------*/
  //ht就是一个码表,hc是文章全部编码完毕后的字符串,w是解码真值对照表
void TrsHuffmanTree(Huffman ht,WeightNode w,HuffmanCode hc,int m,int n){
	int i=0,j,p;
	printf("-----字符串解码信息----\n");
	while(i<m){//i代表遍历编码完毕后的文章的第i个字符
		p=2*n-1;//根节点
		for(j=0;hc[i][j]!='\0';j++){  //针对原文以字符,开始遍历这个字符对应的01序列,遍历完毕也就可以找到在码表ht中是哪个叶子了,叶子里有字符解码值
			if(hc[i][j]=='0')
				p=ht[p].LChild; //左孩子的位置
			else
				p=ht[p].RChild;
		}
		printf("%c",w[p].c);//最终叶子的.c就是取叶子真正的值,也就是解码后的值
		i++;//下一条待解码字符串
	}
}

/*--------解码---------*/
#if 0
/*主函数*/
void main(){
	int i;
	int n=0;//叶子结点的个数
	int m=0;//文章的长度
	char ch[N];//文章
	Huffman ht;//树
	HuffmanCode hc,h;//hc存放所有结点的编码,h存放叶子结点的编码
	WeightNode weight;//叶子结点
	printf("输入待编码字符:\n");
	gets(ch);
	CreateWeight(ch,&m,weight,&n);//计算文章中每个字母的权值和出现字母个数
	printf("结点信息:\n");
	for(i=1;i<=n;i++){
		printf("%c",weight[i].c);
	}
	printf("对应权值信息:\n");
	for(i=1;i<=n;i++){
		printf("%c",weight[i].weight);
	}
	CreateHuffmanTree(ht,weight,n);//利用权值结点创建树
	CrtHuffmanNodeCode(ht,h,weight,m,n);//把树的叶子的编码结果存入h中
	CrtHuffmanCode(ch,h,hc,weight,m,n);//把整片文章的字符都替换成编码,放入数组hc
	TrsHuffmanTree(ht,weight,hc,m,n);//解码就是队原文章每个字符的01序列遍历寻找树中对应的是哪个叶子
	//释放内存代码省略.....
}
/*主函数*/
/*------------------------霍夫曼编码的实现---------------------------*/


 

 

你可能感兴趣的:(重读数据结构之-- 霍夫曼编码)