哈夫曼算法及其应用.docx
《哈夫曼算法及其应用.docx》由会员分享,可在线阅读,更多相关《哈夫曼算法及其应用.docx(17页珍藏版)》请在冰豆网上搜索。
哈夫曼算法及其应用
哈夫曼算法及其应用
一、问题描述
给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。
哈夫曼编码是一种根据哈夫曼树对文件进行编码的方式。
哈夫曼编码是可变字长编码的一种。
本次课程设计是对一个已建文本文件,统计该文件中各字符频率,对各字符进行Huffman编码,将该文件翻译成Huffman编码文件,再将Huffman编码文件翻译成原文件。
压缩文件即读文件,统计文件中的字符个数,对文件进行哈夫曼编码和译码,并将编码译码后的字符存储在文件中。
二、基本要求
程序要求实现以下功能:
1.统计文本文件中各字符的出现次数(涉及读文件,统计字符个数);
2.对文件中的字符进行哈夫曼编码,并存储入字符编码文件;
3.根据字符编码文件对文本文件内容进行编码;
4.根据字符编码文件和已编码文件的内容进行译码;
5.能够输出原文、编码表、文本文件编码、译文。
3、测试数据
Initsmedicalliterature,theFoodandDrugAdministrationstatesthathotwatercomfortableenoughforwashinghandsisnothotenoughtokillbacteria,butismoreeffectivethancoldwaterbecauseitremovesoilsfromthehandthatcanharborbacteria.
四、算法思想
1、哈夫曼树建立算法:
1)根据给定的n个权值{W1,W2,W3……Wn}构成n棵二叉树的集合{T1,T2,……Tn},其中Ti中只有一个权值为Wi的根结点,左右子树均为空。
2)在F中选取两棵根结点的权值最小的树作为左、右子树一构造一棵新的二叉树,且置新的二叉树的根结点的权值为左、右子树上根结点的权值之和。
3)在F中删除这两棵中权值最小的树,同时将新得到的二叉树加入F中。
4)重复2)3)直到F中仅剩一棵树为止,这棵树就是哈夫曼树。
2、哈夫曼编码算法:
通过从哈夫曼树根结点开始,对左子树分配代码“1”,右子树分配代码“0”,一直到达叶子结点为止,然后将从树根沿每条路径到达叶子结点的代码排列起来,便得到了哈夫曼编码。
3、对文件字符编码算法:
逐一读取文件中字符,在哈夫曼编码表查找对应字符,读取其编码并写入文件,如此循环直至结束。
4、哈夫曼译码算法:
根据编码用的哈夫曼树,从根结点出发,逐个读入电文中的二进制码;若代码为“1”,则走左子树的根结点,否则走向右子树的根结点;一旦到达叶子结点,便译出代码所对应的字符。
然后又重新从根结点开始继续译码,直到二进制电文结束。
五、模块划分
1.VoidInitHT(HuffmanTT)
初始化Huffman树。
2.VoidSelectMin(HuffmanTT,intn,int&p1,int&p2)
找到权重最小的叶子。
3.VoidLoadHuffmanFile(HuffmanTT)
加载文件。
4.VoidCreatHT(HuffmanTT)
构造Huffman树。
5.VoidCharSetHuffmanEncoding(HuffmanTT,HuffmanCodeH)
根据Huffman树求Huffman编码表。
6.VoidEncodingHuffmanT(HuffmanTT,HuffmanCodeH)
对文件编码。
7.VoidDecdingHuffmanT(HuffmanTT,HuffmanCodeH)
根据Huffman编码、译码。
8.VoidPrintHuffmanT(HuffmanTT)
打印Huffman权重表。
9.VoidPrintHuffmanH(HuffmanTT,HuffmanCodeH)
打印Huffman编码表。
10.VoidMainMenue()
主菜单。
提供相关的操作提示。
11.Intmain()
主函数。
用个while循环和switch选择结构进行进行循环交互性操作。
六、数据结构//(ADT)
1、哈夫曼树的存储结构:
typedefstruct{
charch;//字符
intweight;//字符权重
intlchild;//左子
intrchild;//右子
intparent;//双亲
}THNODE;
2、哈夫曼编码表的存储结构:
typedefstruct{
charch;//存储字符
charbits[MAX_C+1];//字符编码位串
}CodeNode;
七、源程序
//Huffman.cpp源代码如下:
#include
#include
#include
#defineMAX_C256//定义最大字符数
#defineMAX_N512//定义最大Huffman节点个数
#defineN50
/*HuffmanTree结构*/
typedefstruct
{charch;//字符
intweight;//字符权重
intlchild;//左子
intrchild;//右子
intparent;//双亲
}THNODE;
typedefTHNODEHuffmanT[MAX_N];
/******Huffman编码表结构*****/
typedefstruct
{charch;//存储字符
charbits[MAX_C+1];//字符编码位串
}CodeNode;
typedefCodeNodeHuffmanCode[MAX_C];
HuffmanCodeH;
/*********全局变量**********/
intn;//指示待编译文件的字长
charfilename[20];
/*初始化Huffman树*/
voidInitHT(HuffmanTT)
{inti;
for(i=0;i{T[i].ch='\0';
T[i].weight=0;
T[i].lchild=-1;
T[i].rchild=-1;
T[i].parent=-1;
}
}
/*找到权重最小的叶子*/
voidSelectMin(HuffmanTT,intn,int&p1,int&p2)
{inti;
intj;
for(i=0;i<2*n-1;i++)
{
if(T[i].parent==-1&&T[i].weight>0)
{
p1=i;
break;
}
}
for(j=i+1;j<2*n-1;j++)
{
if(T[j].parent==-1&&T[j].weight>0)
{
p2=j;
break;
}
}
for(i=0;i<2*n-1;i++)
{
if((T[p1].weight>T[i].weight)&&(T[i].parent==-1)&&(p2!
=i)&&(T[i].weight>0))
p1=i;
}
for(j=0;j<2*n-1;j++)
{
if((T[p2].weight>T[j].weight)&&(T[j].parent==-1)&&(p1!
=j)&&(T[j].weight>0))
p2=j;
}
}
/*******加载文件*******/
voidLoadHuffmanFile(HuffmanTT)
{unsignedinti;
intj=0;
charc;
inta[MAX_C];
FILE*fp;
printf("Inputfilename:
");
scanf("%s",filename);
if((fp=fopen(filename,"rb"))==NULL)
{printf("Can'topen%s\n",filename);
exit(0);
}
for(i=0;ia[i]=0;
fseek(fp,0,SEEK_SET);
while
(1)//(!
feof(fp))
{
fread(&c,sizeof(unsignedchar),1,fp);
if(feof(fp))break;
a[(unsignedint)c]++;
}
fclose(fp);
/*统计输入文件的字符及其权重并存放到树T[]*/
for(i=0;i{
if(a[i]!
=0)
{
T[j].ch=(unsignedchar)i;
T[j++].weight=(unsignedint)a[i];
}
}
n=j;
}
/*******构造huffam树,T[2*n-1]为其根********/
voidCreatHT(HuffmanTT)
{
inti,p1,p2;
LoadHuffmanFile(T);//加载被编码文件
for(i=n;i<2*n-1;i++)
{SelectMin(T,i-1,p1,p2);
T[p1].parent=T[p2].parent=i;
T[i].lchild=p1;
T[i].rchild=p2;
T[i].weight=T[p1].weight+T[p2].weight;
}
}
/*根据HuffmanT求Huffman编码表H*/
voidCharSetHuffmanEncoding(HuffmanTT,HuffmanCodeH)
{intc;//指示T中孩子的位置
intp;//指示T中双亲的位置
inti;
intstart;//指示编码在cd中的位置
charcd[N];//临时存放编码
for(i=0;i{H[i].ch=T[i].ch;//读入叶子T[i]对应的字符
if(i==0||i==1)start=n;
elsestart=n-i+1;
cd[--start]='\0';//编码起始位置的初值
c=i;//从叶子T[i]开始回溯
while((p=T[c].parent)>=0)//直到回溯到T[c]是树根位置
{cd[--start]=(T[p].lchild==c)?
'0':
'1';
c=p;}
strcpy(H[i].bits,&cd[start]);//复制临时编码到编码表中
}
}
/*对文件编码,将结果保存到codefile.txt中*/
voidEncodingHuffmanT(HuffmanTT,HuffmanCodeH)
{charc;
FILE*in,*fp;
intj,l;
charencodefile[20],temp[MAX_C];
if((in=fopen(filename,"rb"))==NULL)
{printf("Read%sfail!
\n",encodefile);
exit
(1);}
CharSetHuffmanEncoding(T,H);
printf("Inputencodefilename:
");
gets(encodefile);
if((fp=fopen(encodefile,"wb"))==NULL)
{printf("Write%sfail!
\n",encodefile);
exit
(1);}
fread(&c,sizeof(unsignedchar),1,in);
fwrite(&c,sizeof(unsignedchar),1,fp);
fseek(in,0,SEEK_SET);
fseek(fp,0,SEEK_SET);
while
(1)//(!
feof(in))
{fread(&c,sizeof(unsignedchar),1,in);
if(feof(in))break;
for(j=0;j{if(c==H[j].ch)
{l=0;
while(H[j].bits[l]!
='\0')
{temp[l]=H[j].bits[l];
l++;}
intm=0;
while(l--)
{fwrite(&temp[m++],sizeof(unsignedchar),1,fp);}
}
}
}
fclose(fp);
printf("Encodingfilehassavedinto%s!
\n",encodefile);
}
/*根据Huffman编码、译码*/
voidDecodingHuffmanT(HuffmanTT,HuffmanCodeH)
{inti;//指示Huffmantree叶子个数
FILE*fp,*fp1;
charch,ch1[20],ch2[20];
printf("Inputencodefilename:
");
scanf("%s",ch1);
printf("Inputdecodefilename:
");
scanf("%s",ch2);
fp=fopen(ch1,"rb");
fp1=fopen(ch2,"wb");
//根据Huffman树对Huffman编码译码
i=2*n-2;
fseek(fp,0L,SEEK_SET);
fseek(fp1,0L,SEEK_SET);
while(!
feof(fp))
{fread(&ch,sizeof(unsignedchar),1,fp);
if(ch=='0')//若编码为o,则找此结点的左子树;
i=T[i].lchild;
if(ch=='1')//若编码为1,则找此结点的右子树;
i=T[i].rchild;
if(i{fwrite(&T[i].ch,sizeof(unsignedchar),1,fp1);
i=2*n-2;
}
}
fclose(fp);
fclose(fp1);
printf("Decodingaccomplished!
\nTheresulthassaveinput%s.\n",ch2);
getchar();
}
/*打印Huffman权重表*/
voidPrintHuffmanT(HuffmanTT)
{inti;
FILE*fp;
if((fp=fopen("treeprint.txt","wb"))==NULL)
{printf("Opentreeprint.txtfail!
\n");
exit
(1);
}
printf("\nLeaf&weightoftheHuffmantreeisbelow:
\n");
for(i=0;i{if(i%5==0&&i>0)
printf("\n");
if(T[i].weight>0)
{fprintf(fp,"%c:
%d",T[i].ch,T[i].weight);
printf("%c:
%d",T[i].ch,T[i].weight);
}
}
fclose(fp);
printf("\nLeaf&weightoftheHuffmantreesavedintreeprint.txt\n\n");
}
/*打印Huffman编码表*/
voidPrintHuffmanH(HuffmanTT,HuffmanCodeH)
{inti;
FILE*fp;
CharSetHuffmanEncoding(T,H);
if((fp=fopen("codeprint.txt","wb"))==NULL)
{printf("Opencodeprint.txtfail!
\n");
exit
(1);
}
for(i=0;i{if(i%10==0&&i>0)printf("\n");
printf("%c:
%s\n",T[i].ch,H[i].bits);
fprintf(fp,"%c:
%s",T[i].ch,H[i].bits);
}
fclose(fp);
printf("\nHuffmantreecodesavedincodeprint.txt!
\n\n");
}
/*主菜单*/
voidMainMenue()
{fflush(stdin);
printf("\n**********************MainMenue**************************\n");
printf("****\n");
printf("**1.Loadtobedealtfile.**\n");
printf("**2.ShowHuffmancodelist.**\n");
printf("**3.ShowHuffmanweightlist.**\n");
printf("**4.EncodingHuffmanfile.**\n");
printf("**5.DecodingHuffmanfile.**\n");
printf("**6.Exit.**\n");
printf("****\n");
printf("************************************************************\n");
}
/*主函数开始*/
intmain()
{
intflag=1;
charch[10];
HuffmanTT;//定义Huffman树
HuffmanCodeH;//定义Huffman编码表
InitHT(T);//初始化Huffman树
while(flag)
{MainMenue();
printf("Pleaseinputyourchoice(1~6):
");
gets(ch);
switch(ch[0])
{
case'1':
CreatHT(T);break;
case'2':
PrintHuffmanH(T,H);break;
case'3':
PrintHuffmanT(T);break;
case'4':
EncodingHuffmanT(T,H);break;
case'5':
DecodingHuffmanT(T,H);break;
case'6':
exit
(1);
default:
printf("Inputerror!
\n");break;
}
}
return0;
}
八、测试情况
程序的测试结果如下:
建立哈夫曼树、打印编码表正确。
打印权重表正确。
哈夫曼编码正确。
哈夫曼译码正确,退出正确。
九、参考文献
1、严蔚敏,《数据结构C语言》,清华大学出版社。
2、谭浩强,《c语言程序设计》,清华大学出版社。
小结
课程设计是《数据结构》课程教学必不可缺的一个重要环节,是培养学生综合运用所学知识,发现、提出、分析和解决实际问题,锻炼实践能力的重要环节。
同时可加深学生对该课程所学内容的进一步的理解与巩固,是将计算机课程与实际问题相联接的关键步骤。
通过课程设计,能够提高分析问题、解决问题,从而运用所学知识解决实际问题的能力。
通过本次数据结构的课程设计,我理解很多之前上课没懂的知识,对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。
当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必定会顺利地做出来。
这次课程设计是我们大三来做的第一次了,我在编辑中犯了不应有的错误,如设计统计字符和合并时忘记应该怎样保存保存数据,在其他同学的帮助下明确并改正了错误和疏漏,使我们的程序有了更高的质量。
在设计过程中逐渐积累了一些基本的经验,所以做到后来逐渐顺利,但也还是会遇到一些这样或那样的问题,通过本次课程设计,进一步适应团队合作开发规模稍大的程序,体会个人开发与团队开发的利弊,加强团队协作与团队内的沟通,表达能力。
感受对本专业说学习的多门课程知识进行综合应用,将多门课程的知识融合贯通。
同时通过这次设计也获得一些编程经验:
编程时要认真,出现错误要及时找出并改正,遇到问题要去查相关的资料。
反复的调试程序,把各个注意的问题要想到;同时要形成自己的调试程序的风格,从每个细节出发,不放过每个知识点。
另外,要注意符号的使用。
本次课程设计虽然实现了哈夫曼编码/译码等要求,但由于时间等各方面原因,仍有一些问题没有解决,例如一些要求输入判断时判断不严格、操作界面不够美观等,总体上还有待改进。
1)
2)
3)