哈弗曼树创建并实现编码译码
采用C语言实现从文件读取数据,构造创建哈弗曼树,实现编码译码过程。
1.哈弗曼树的基本概念
哈弗曼树,又称为最优二叉树,是带权路径最短的树,可用来构造最优编码,用于信息传输,数据压缩等方面,是一种广泛的二叉应用。
需要了解的基本概念:
– 路径:从树的一个节点到另一个节点之间的分支序列构成两个节点的路径
– 路径程度:路径上分支的条数
– 树的路径长度:从跟节点到每个节点路径的长度之和
– 节点的权: 给数种节点赋予一个数值
– 带权路径长度:节点到树根间的路径长度与节点的权值的乘积
– 树的带权路径长度:树中所有叶子节点的带权路径长度之和,成为树的带权路径长度,记为WPL。
– 最优二叉树:在叶子节点个数n以及各叶子的权值确定的条件下,树的带权路径长度WPL最小的二叉树。
例如:如下三棵树的权值分别为{9,6,3,1}的四个叶子构成,其带权路径长度为:
可以验证:c树是有四个叶子且权值分别为{9,6,3,1}的一棵最优二叉树
2 . 哈弗曼编码原理:选择前缀编码,即:同一字符集中任何一个字符的编码都不是另一个字符编码的前缀。例如:对于字符集{A,B,C,D},编码集对应为{1,01,000,001}。另一方面,为了有效的压缩信息,则出现频率高的字符编码应该短一些,而出现字符频率小字符编码则长一些,观察哈弗曼树可以发现,权值大的叶子距根近,权值小的反之。因此,可以利用哈弗曼树的根到叶子节点的路径设计编码。每一个字符对应一个叶子,每一个权值对应出现频率。 哈弗曼编码是最优二进制前缀编码。
3. 哈弗曼译码原理:译码分解、识别、还原数据的过程。从哈弗曼树的根出发,根据每位编码的0或1确定进入左子树还是右子树,直到叶子节点。
4.C语言编程实现过程
– 哈弗曼树建立的数据结构:
weight:节点的权值
LChild:左孩子
RChild:右孩子
Parent:双亲
data:存储字符数据
typedef struct CodeNode
{
char ch;
char bits[9];//存放编码位置
int len;//编码长度
}HuffmanCode[MAX];
变量声明:
int wt[MAX];//存放每个字符对应的权值
int Wt[MAX];
char s[MAX];//存放文件中的字符
char str[MAX];//存放文件中的字符种类
char chara[MAX];
int m;//字符种类数
int sum;//字符总个数
int cn=0;//字符频率
char filename[20];
提取文件中的字符,用哈希表统计字符频率,哈希表数组下标数值对应字符的ACSII码大小,哈希数组中对应存储权值
void Get(huffmanTree Ht,chars[])
{ FILE *fp;
char ch;
int i=1;
printf("请输入你要打开文件的名字:");
scanf("%s",filename);
fp=fopen(filename,"r");
if(fp==NULL)
{ printf("\t\tCan not open thefile");
system("pause");
}
else
printf("\t\tOpen the filesuccess!\n");
ch=fgetc(fp);
while(ch!=EOF)
{
s[i++]=ch;//存储文件中的字符
sum++;//存储文件中的字符总个数
wt[ch]++;//存储字符权值
ch=fgetc(fp);
}
fclose(fp);
}
统计字符种类数及对应的权值
int Statistic()
{ int i=1,j;
for(j=1;j<MAX;j++)
if(wt[j]!=NULL)
{Wt[i++]=wt[j];
cn++;
}
return cn;
}
选取最小与次小值,用来创建叶子
void select(huffmanTree h,int n,int *s1,int *s2)
{
int i;
int p,q;
*s1=*s2=0;
p=q=MAX;
for(i=1;i<=n;i++)
{
if(h[i].parent==0&&h[i].weight<p)
{
q=p;
*s2=*s1;
p=h[i].weight;
*s1=i;
}
else if(h[i].parent==0&&h[i].weight<q)
{
q=h[i].weight;
*s2=i;
}
}
}
哈弗曼树的创建
void CreatHuffmanTree(huffmanTree Ht,int Wt[],int n,HuffmanCode HC)
{
int i;
int j=1;
int s1,s2;
//对树进行初始化
for(i=1;i<=2*n-1;i++)
{ Ht[i].data='0';
Ht[i].parent=0;
Ht[i].Lchild=0;
Ht[i].Rchild=0;
Ht[i].weight=0;
}
for(i=1;i<=n;i++)
{
Ht[i].weight=Wt[i]; //对叶子节点进行权值的赋值
}
for(i=n+1;i<=2*n-1;i++)
{
select(Ht,i-1,&s1,&s2);//选取最小与次小值
Ht[i].weight=Ht[s1].weight+Ht[s2].weight;
Ht[i].Lchild=s1;
Ht[i].Rchild=s2;
Ht[s1].parent=i;
Ht[s2].parent=i;
}
for(i=1;i<MAX;i++)
if(wt[i]!=0)
HC[j++].ch=(char)i;
for(i=1;i<=m;i++)
Ht[i].data=HC[i].ch;
//打印哈弗曼树
printf("\tdata\tweight\tparebt\tLchild\tRchild\n");
for(i=1;i<=2*n-1;i++)
{ printf("\t%c",Ht[i].data);
printf("\t%d",Ht[i].weight);
printf("\t%d",Ht[i].parent);
printf("\t%d",Ht[i].Lchild);
printf("\t%d",Ht[i].Rchild);
printf("\n");
}
for(i=1;i<MAX;i++)
if(wt[i]!=0)
HC[j++].ch=(char)i;
printf("\n\n\t====>文件中的字符种类及所对应的权值<====\n");
for(i=1;i<=m;i++)
printf("\t\t<%c,%d>\n",HC[i].ch,Wt[i]);
printf("\n");
system("pause");
system("cls");
printf("\n\n\t\t\t哈夫曼树创建成功!\2\n\n\n\t\t\t\t\t\t");
system("pause");
}
根据创建好的哈弗曼树对文件中含有的每种字符进行编码:
/*哈弗曼编码*/
void HuffmanEncoding(huffmanTree Ht,HuffmanCode HC)
{
int c,p,i;
char cd[MAX];
int start;
cd[m]='\0';
for(i=1;i<=m;i++)
{
start=m;
c=i;
p=Ht[c].parent;
while(p>0)
{ --start;
if (Ht[p].Lchild==c)
cd[start]='0'; //左孩子编码为0
else if(Ht[p].Rchild==c)
cd[start]='1';//右孩子编码为1
c=p;
p=Ht[c].parent;
}
strcpy(HC[i].bits,&cd[start]);
HC[i].len=m-start;
}
}
对目标文件进行编码并存储至文件中,对文件中的字符依次进行编码,依次读取字符,已经编码的字符进行一一遍历比对,这种方法比较耗时,因为这个代码写的比较早,当时还没有接触算法,大家可以优化下
/*对文件进行编码*/
void coding(HuffmanCode HC,char *s )
{ system("cls");
int i,j,k;
FILE *fp1;
printf("==>编码中...\n\n");
printf("请输入你将要写入文件的名字:");
scanf("%s",filename);
fp1=fopen(filename,"w");
if(fp1==NULL)
{ printf("\t\tCan not open the file");
system("pause");
}
else
printf("\t\tOpen the file success!\n\n\n");
printf("\t原文件为:");
for(i=1;i<=sum;i++)
printf("%c",s[i]);
printf("\n");
printf("\n\t文件的编码为:");
for(j=1;j<=sum;j++)
for(i=1;i<=m;i++)
if(s[j]==HC[i].ch)
{
for(k=0;k<=HC[i].len;k++)
{ printf("%c",HC[i].bits[k]);
fputc(HC[i].bits[k],fp1);//将编码写入文件中
}
break;
}
system("pause");
fclose(fp1);
system("cls");
}
对已经编码的文件进行译码:
void decode(HuffmanCode HC,huffmanTree Ht)//对编码文件进行译码
{
FILE *fp1,*fp2;
char ch;
int p,k;
char filename2[20];
printf("==>译码中...\n\n");
if((fp1=fopen(filename,"r"))==NULL)
{
printf("文件不能创建!");
exit(1);
}
printf("请输入你将译码存放的文件名字:");
scanf("%s",filename2);
fp2=fopen(filename2,"w");
if(fp2==NULL)
{
printf("文件不能创建!");
exit(1);
}
p=k=2*m-1;
ch=fgetc(fp1);
printf("译码为:");
while(ch!=EOF)
{
if(ch=='0')
p=Ht[p].Lchild;
else if(ch=='1')
p=Ht[p].Rchild;
if(Ht[p].data!='0')
{
printf("%c",Ht[p].data);
fputc(Ht[p].data,fp2);
p=k;
}
ch=fgetc(fp1);
}
system("pause");
printf("\n");
fclose(fp1);
fclose(fp2);
}
程序的主函数为:
int main()
{
huffmanTree Ht;
HuffmanCode HC;
printf("\t\t\t\t功能简介:\n");
printf("\t\tNO.1 获取文件中的字符及频率,建立哈夫曼树\n");
printf("\t\tNO.2 对原文件进行哈弗曼编码\n");
printf("\t\tNO.3 编码后的文件译码\n");
Get(Ht,s);
m=Statistic();
CreatHuffmanTree(Ht,Wt,m,HC);
HuffmanEncoding(Ht,HC);
coding(HC,s);
decode(HC,Ht);
return 0;
}
再次声明:本文章中的代码写于2013年左右,近期整理下,有的地方算法设计不是很合理,大家可以参考改进哈~
- –