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序列遍历寻找树中对应的是哪个叶子 //释放内存代码省略..... } /*主函数*/ /*------------------------霍夫曼编码的实现---------------------------*/