在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。
例如,在英文中,e的出现机率最高,而z的出现概率则最低。当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节,即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。
霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明霍夫曼树的WPL是最小的。
1、哈夫曼编码
在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“AFTER DATA EAR ARE ART AREA”,这里用到的字符集为“A,E,R,T,F,D”,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用000、001、010、011、100、101对“A,E,R,T,F,D”进行编码发送,当对方接收报文时再按照三位一分进行译码。显然编码的长度取决报文中不同字符的个数。若报文中可能出现26个不同字符,则固定编码长度为5。然而,传送报文时总是希望总长度尽可能短。在实际应用中,各个字符的出现频度或使用次数是不相同的,如A、B、C的使用频率远远高于X、Y、Z,自然会想到设计编码时,让使用频率高的用短码,使用频率低的用长码,以优化整个报文编码。
为使不等长编码为前缀编码(即要求一个字符的编码不能是另一个字符编码的前缀),可用字符集中的每个字符作为叶子结点生成一棵编码二叉树,为了获得传送报文的最短长度,可将每个字符的出现频率作为字符结点的权值赋予该结点上,显然字使用频率越小权值越小,权值越小叶子就越靠下,于是频率小编码长,频率高编码短,这样就保证了此树的最小带权路径长度效果上就是传送报文的最短长度。因此,求传送报文的最短长度问题转化为求由字符集中的所有字符作为叶子结点,由字符出现频率作为其权值所产生的哈夫曼树的问题。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证报文编码总长最短。
哈夫曼静态编码:它对需要编码的数据进行两遍扫描:第一遍统计原数据中各字符出现的频率,利用得到的频率值创建哈夫曼树,并必须把树的信息保存起来,即把字符0-255(28=256)的频率值以2-4BYTES的长度顺序存储起来,(用4Bytes的长度存储频率值,频率值的表示范围为0–232-1,这已足够表示大文件中字符出现的频率了)以便解压时创建同样的哈夫曼树进行解压;第二遍则根据第一遍扫描得到的哈夫曼树进行编码,并把编码后得到的码字存储起来。
哈夫曼动态编码:动态哈夫曼编码使用一棵动态变化的哈夫曼树,对第t+1个字符的编码是根据原始数据中前t个字符得到的哈夫曼树来进行的,编码和解码使用相同的初始哈夫曼树,每处理完一个字符,编码和解码使用相同的方法修改哈夫曼树,所以没有必要为解码而保存哈夫曼树的信息。编码和解码一个字符所需的时间与该字符的编码长度成正比,所以动态哈夫曼编码可实时进行。
2、哈夫曼译码
在通信中,若将字符用哈夫曼编码形式发送出去,对方接收到编码后,将编码还原成字符的过程,称为哈夫曼译码。
咳咳,拿着小板凳,学会Ctrl+c和Ctrl+v,这也是一种技巧
#include<stdio.h>
#include<malloc.h>
#include<string.h>
typedef struct HTNode
{
char charname;
int weight;
int parent, lchild, rchild;
}HTNode, *HuffmanTree;
typedef char **HuffmanCode; //动态分配数组存储字符编码的动态空间
int n=27,m=53; // m=2*n-1 m为根节点个数
char *a;
int *b;
void HuffmanCoding(HuffmanTree HT,HuffmanCode HC,char *a,int *b,int n)
{
int i;//通用整型变量i,用于循环中
int s1 = 0; //变量s1用于哈夫曼树的创建
int s2 = 0;//变量s2用于哈夫曼树的创建
int j;
int d;
int m=2 * n - 1;
char *cd=(char*)malloc(n*sizeof(char)); //分配临时存放每个字符编码的动态空间,译码使用
//构造哈夫曼树HT
if(n<=1)
{
return ;
}//n值小于等于1,不满足创建哈弗曼树的条件
//HT=(HuffmanTree)malloc(((2*n-1)+1)*sizeof(HTNode));
for(i=1;i<=n;++i)
{
HT[i].charname=a[i-1];
HT[i].weight=b[i-1];
HT[i].parent=0;
HT[i].lchild=-1;
HT[i].rchild=-1;
}
for(;i<=m;++i) //所要产生结点初始化所要产生结点
{
HT[i].charname='0';
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=-1;
HT[i].rchild=-1;
}
//以下功能为寻找权值字最小的两个值
for(i=n+1;i<=m;++i) //用于筛选出两个权重最小的两个
{
for(s1=1;HT[s1].parent!=0;)
{
s1++;
} //双亲不为0,让变量s1加1,寻找双亲为0的结点
for (j=s1;j<=i-1;j++) //用于寻找第一个双亲为0的结点
{
if(HT[j].parent!=0)
{
continue;
}
s1=HT[j].weight<HT[s1].weight?j:s1; //寻找第一个权重最小的下标
}
HT[s1].parent=i;
HT[i].lchild=s1;
for(s2=1;HT[s2].parent!=0;) //同s1作用一样,找第二个双亲为0的结点
{
s2++;
}
for (j=s2;j<=i-1;j++)
{
if(HT[j].parent != 0)
{
continue;
}
s2 = HT[j].weight<HT[s2].weight?j:s2;
}
HT[s2].parent = i;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight+HT[s2].weight; //将两个最小的结点权重相加组成父结点
}
//求每个字符的哈夫曼编码,存储在编码HC中
// HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
cd[n-1]='\0'; //存储编码值,下标n-1的为'\0'结束
for (i=1; i<=n;++i) //逐个字符求哈夫曼编码
{
int start=n-1,c,f; //start开始时指向最后,既编码结束位置
for (c=i,f=HT[i].parent; f!=0;c=f,f=HT[f].parent) //f指向结点c的双亲结点,f!=0从叶子结点向上回溯,直到根结点
{
if(HT[f].lchild==c) //结点c是f的左孩子,则生成代码0
{
cd[--start]='0';
}
else if(HT[f].rchild==c) //结点c是f的右孩子,则生成代码1
{
cd[--start]='1';
}
}
HC[i]=(char*)malloc((n-start) * sizeof(char));//为每个字符分配空间
f=n-start; //继续回溯
for(d=0;d<f;d++,start++) //将cd数组中的字符译码值传送给HC的动态数组中去
{
HC[i][d]=cd[start];
}
}
free(cd); //释放临时空间
}
//信息存储函数
void cunchu_charnanme_Num()
{
int i;
char charName[27]={' ','A','B','C','D','E','F','G','H','I','J','K','L','M','N', //字符存储数组
'O','P','Q','R','S','T','U','V','W','X','Y','Z'} ;
int Weight[27]={'186','64','13','22','32','103','21','15','47','57','1','5','32','20','57', //权重存储数组
'63','15','1','48','51','80','23','8','18','1','16','1'};
a=(char *)malloc(n*sizeof(char)); //给全局变量a分配空间
b=(int *)malloc(n*sizeof(int)); //给全局变量b分配空间
for (i=0;i<n;i++) //将数组charName内容给予数组a
{
a[i]=charName[i];
}
for(i=0;i<n;i++) //将数组Weight内容给予数组b
{
b[i] = Weight[i];
}
}
//显示哈夫曼树
void showHT_AND_showHC(HuffmanTree HT,HuffmanCode HC,int n, int m,char* a)
{
int i=0;
int j=0;
int k=0; //打印循环k
printf("\n");
printf(" >>>>所存储的哈夫曼编码以及译码<<<<\n");
printf(" |__________________________________|\n");
for (i = 1; i <= n; i++) //已二维数组的形式输出哈夫曼树
{
printf(" |**%4c :",a[i-1]); //输出字符
for(k=0;HC[i][k]!='\0'; k++)
{
printf("%c",HC[i][k]); //输出字符对应的译码值
}
printf("%13c",'*');
printf("\n");
}
printf(" |__________________________________|\n");
printf("\n");
}
//查找所需要的字母的编码值
void Find_letter(HuffmanTree HT,HuffmanCode HC,int n, int m,char* a)
{
int i;
int j;
int k;
int num=0;
char find1[100]; //存字符码数组
printf(">>>>>>>>>>>>>请输入所要查找的字符的编码值('#'结束输入):");
for(i=0;;i++) //可循环输入字符,'#'结束输入
{
num++;
scanf("%c",&find1[i]);
if(find1[i]=='#')
{
break;
}
}
printf("\n");
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>字符的哈夫曼编码是:");
for (i=0;i<num;i++) //打印所找到对应字符的编码值
{
for(k=1;k<n;k++)
{
if(find1[i]==HT[k].charname) //如果输入字符与存储数组字符相等,打印其编码
{
for(j=0;j<=11;j++)
{
printf("%c",HC[k][j]);
}
}
}
//printf("\n");
}
printf("\n");
printf("\n");
}
void Find_num(HuffmanTree HT,HuffmanCode HC,int n, int m,char* a)
{
int i=0;
int j;
int k=0;
int c=0;
char find2[100]; //译码存放数组
printf(">>>>>>>>>>>>请输入所需要查询译码对应字母的值('#'结束输入):");
//scanf("%s",&find2);
gets(find2);
printf("\n");
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>所要查询的字符为:");
while(find2[i]!='#') //如果输入编码值结束不为'#'则进入while循环来找到对应的字符
{
for(j=1;j<28;j++)
{
while(find2[i+k]==HC[j][k]) //如果找到输入译码值与存储译码值相等,进入
{
//printf("0");
k++;
if(HC[j][k]=='\0') //如译码数组结束'\0'
{
k--;
printf("%c",HT[j].charname); //打印对应的字符
break;
}
}
if(find2[i+k]!=HC[j][k]) //不相等跳出循环,判断下一个二维数组值是否相等,直到找到为止
{
k=0;
continue;
}
else
{
i=i+k+1; //相等时让i=i+k+1,判断第二个字符编码值,如果没有则跳出
k=0;
continue;
}
}
}
printf("\n");
}
void main()
{
int t;
char ss;
HuffmanTree HT;
HuffmanCode HC;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); //动态分配m+1个单元
HC=(HuffmanCode)malloc(n*sizeof(HuffmanCode)); //动态分配n个单元
cunchu_charnanme_Num(); //存储函数
HuffmanCoding(HT, HC, a, b, n); //存储译码函数
while(t!=4) //输入不同的值进行不同操作
{ printf(" 请输入相应字符开始执行程序\n");
printf("\n");
printf(" >>>>>1. 输出存储的哈夫曼编码和译码值 <<<<<\n");
printf(" >>>>>2.输入字符获取其对应的二进制译码<<<<<\n");
printf(" >>>>>3. 输入二进制获取其对应的字符 <<<<<\n");
printf(" >>>>>4. 退出 <<<<<\n");
printf(" >>>>>>>>>>>>>>>输入<<<<<<<<<<<<<<<\n");
printf(" 指令: ");
scanf("%d",&t);
switch(t)
{
case 1:
showHT_AND_showHC(HT, HC, n, m, a); //输入1,打印存储哈夫曼
break;
case 2:
Find_letter(HT, HC, n, m, a); //输入2,寻找字符对应编码值
break;
case 3:
scanf("%c",&ss);
Find_num(HT, HC, n, m, a); //输入3,寻找编码对应字符
break;
case 4:
printf(" >>>>>>>>>>>>>>>退出<<<<<<<<<<<<<<<\n"); //输入4,退出系统
break;
}
}
}
该注释懂得的地方我已经表明,相信大家一目了然!
一个简单的哈夫曼译码和编码就完成了,是不是简简单单、明明白白、一目了然,拜了个拜!