数据结构哈夫曼编码.docx
《数据结构哈夫曼编码.docx》由会员分享,可在线阅读,更多相关《数据结构哈夫曼编码.docx(21页珍藏版)》请在冰点文库上搜索。
数据结构哈夫曼编码
摘要利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。
这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原),将所得结果存储到已开辟空间中。
试为这样的信息收发站编写一个哈夫曼码的编/译码系统。
本程序可以对任何大小的字符型文件进行Huffman编码,生成一个编码文件。
并可以在程序运行结束后的任意时间对它解码还原生成字符文件。
即:
先对一条电文进行输入,并实现Huffman编码,然后对Huffman编码生成的代码串进行译码,最后输出电文数字
AbstractUsehoffmanncodewascommunicationcanimprovetheutilizationrateofchannel,shortentheinformationtransmissiontime,reducethetransmissioncost.Thisrequirementinthesendingendthroughacodingsysteminadvancetotransmitdatacoding,inthereceiverwillspreadofthedatadecoding(restoration),willtheresultsofopenspacetostorealready.Tryforsuchinformationsendingandreceivingstationwriteahoffmannyardsofmakeup/decodingsystem.ThisprogramcanonanyofthesizeofthefiletypecharacterHuffmancoding,generatesacodefiles.Andcantherunningoftheapplicationatanytimeaftertheendofthereductionofdecodingitgeneratedcharacterfiles.Namely:
firsttoamessageforinput,andrealizetheHuffmancoding,andthentoHuffmancodinggeneratedcodestringdecode,finallydigitaloutputmessage
关键字哈夫曼编码;哈夫曼译码
KeywordsHoffmanncode;Hoffmanndecode
1设计要求
对输入的一串电文字符实现哈夫曼编码,输出电文字符串。
通常我们把数据压缩的过程称为编码。
电报通信是传递文字的二进制码形式的字符串。
但在信息传递时,总希望总长度能尽可能短,即采用最短码。
假设每种字符在电文中出现的次数为Wi,编码长度为Li,电文中有n种字符,则电文编码总长度为∑WiLi。
若将此对应到二叉树上,Wi为叶结点的权,Li为根结点到叶结点的路径长度。
那么,∑WiLi恰好为二叉树上带权路径长度。
因此,设计电文总长最短的二进制前缀编码,就是以n种字符出现的频率作权,构造一棵哈夫曼树,此构造过程称为哈夫曼编码。
设计实现的功能:
(1)哈夫曼树的建立;
(2)哈夫曼编码的生成;(3)对编码文件的译码。
2系统结构图(功能模块图)
图2-1哈夫曼结构图
3详细设计
3.1哈夫曼树的建立
3.1.1哈夫曼树的存储结构描述为:
#defineN50//叶子结点数
#defineM2*N-1//哈夫曼树中结点总数
typedefstruct{
intweight;//叶子结点的权值
intlchild,rchild,parent;//左右孩子及双亲指针
}HTNode;//树中结点类型
typedefHTNodeHuffmanTree[M+1];
3.1.2哈夫曼树的算法:
voidCreateHT(HTNodeht[],intn)//调用输入的数组ht[]和节点数n
{
inti,k,lnode,rnode;
intmin1,min2;
for(i=0;i<2*n-1;i++)
ht[i].parent=ht[i].lchild=ht[i].rchild=-1;//所有结点的相关域置初值-1
for(i=n;i<2*n-1;i++)//构造哈夫曼树
{
min1=min2=32767;//int的范围是-32768—32767
lnode=rnode=-1;//lnode和rnode记录最小权值的两个结点位置
for(k=0;k<=i-1;k++)
{
if(ht[k].parent==-1)//只在尚未构造二叉树的结点中查找
{
if(ht[k].weight{
min2=min1;rnode=lnode;
min1=ht[k].weight;lnode=k;
}
elseif(ht[k].weight{
min2=ht[k].weight;rnode=k;
}
}
}
ht[lnode].parent=i;ht[rnode].parent=i;//两个最小节点的父节点是i
ht[i].weight=ht[lnode].weight+ht[rnode].weight;//两个最小节点的父节点权值为两个最小节点权值之和
ht[i].lchild=lnode;ht[i].rchild=rnode;//父节点的左节点和右节点
}
}
3.2哈夫曼编码
voidCreateHCode(HTNodeht[],HCodehcd[],intn)
{
inti,f,c;
HCodehc;
for(i=0;i{
hc.start=n;c=i;
f=ht[i].parent;
while(f!
=-1)//循序直到树根结点结束循环
{
if(ht[f].lchild==c)//处理左孩子结点
hc.cd[hc.start--]='0';
else//处理右孩子结点
hc.cd[hc.start--]='1';
c=f;f=ht[f].parent;
}
hc.start++;//start指向哈夫曼编码hc.cd[]中最开始字符
hcd[i]=hc;
}
}
voidDispHCode(HTNodeht[],HCodehcd[],intn)//输出哈夫曼编码的列表
{
inti,k;
printf("输出哈夫曼编码:
\n");
for(i=0;i{
printf("%c:
\t",ht[i].data);
for(k=hcd[i].start;k<=n;k++)//输出所有data中数据的编码
{
printf("%c",hcd[i].cd[k]);
}
printf("\n");
}
}
voideditHCode(HTNodeht[],HCodehcd[],intn)//编码函数
{
charstring[MAXSIZE];
inti,j,k;
scanf("%s",string);//把要进行编码的字符串存入string数组中
printf("\n输出编码结果:
\n");
for(i=0;string[i]!
='#';i++)//#为终止标志
{
for(j=0;j{
if(string[i]==ht[j].data)//循环查找与输入字符相同的编号,相同的就输出这个字符的编码
{
for(k=hcd[j].start;k<=n;k++)
{
printf("%c",hcd[j].cd[k]);
}
break;//输出完成后跳出当前for循环
}
}
}
}
3.3哈夫曼译码
voiddeHCode(HTNodeht[],HCodehcd[],intn)//译码函数
{
charcode[MAXSIZE];
inti,j,l,k,m,x;
scanf("%s",code);//把要进行译码的字符串存入code数组中
while(code[0]!
='#')
for(i=0;i{
m=0;//m为想同编码个数的计数器
for(k=hcd[i].start,j=0;k<=n;k++,j++)//j为记录所存储这个字符的编码个数
{
if(code[j]==hcd[i].cd[k])//当有相同编码时m值加1
m++;
}
if(m==j)//当输入的字符串与所存储的编码字符串个数相等时则输出这个的data数据
{
printf("%c",ht[i].data);
for(x=0;code[x-1]!
='#';x++)//把已经使用过的code数组里的字符串删除
{
code[x]=code[x+j];
}
}
}
}
3.4主函数
voidmain()
{
intn=26,i;
charorz,back,flag=1;
charstr[]={'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'};//初始化
intfnum[]={186,64,13,22,32,103,21,15,47,57,1,2,32,20,57,63,15,1,48,51,80,23,8,18,1,16};//初始化
HTNodeht[M];//建立结构体
HCodehcd[N];//建立结构体
for(i=0;i{
ht[i].data=str[i];
ht[i].weight=fnum[i];
}
while(flag)//菜单函数,当flag为0时跳出循环
3.5显示部分源程序:
{
printf("\n");
printf("********************************");
printf("\n**1---------------显示编码**");
printf("\n**2---------------进行编码**");
printf("\n**3---------------进行译码**");
printf("\n**4---------------退出**\n");
printf("***********************************");
printf("\n");
printf("请输入选择的编号:
");
scanf("%c",&orz);
switch(orz)
{
case'a':
case'A':
system("cls");//清屏函数
CreateHT(ht,n);
CreateHCode(ht,hcd,n);
DispHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'b':
case'B':
system("cls");
printf("请输入要进行编码的字符串(以#结束):
\n");
editHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'c':
case'C':
system("cls");
DispHCode(ht,hcd,n);
printf("请输入编码(以#结束):
\n");
deHCode(ht,hcd,n);
printf("\n按任意键返回...");
getch();
system("cls");
break;
case'd':
case'D':
flag=0;
break;
default:
system("cls");
}
}
}
4程序运行结果
进入主菜单
图4-1
选A时的显示结果
图4-2
选B时的显示结果
图4-3
选C时的显示结果
图4-4
5心得体会
通过这次课程设计,我们学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。
我们认识到根据不同的需求,采用不同的数据存储方式,不一定要用栈,二叉树等高级类型,有时用基本的一维数组,只要运用得当,也能达到相同的效果,甚至更佳,就如这次的课程设计,通过用for的多重循环,舍弃多余的循环,提高了程序的运行效率。
这次课程设计,我们在编辑中犯了不应有的错误,设计统计字符和合并时忘记应该怎样保存数据,对文件的操作也很生疏。
在不断分析后明确并改正了错误和疏漏,我们的程序有了更高的质量。
另外,互帮互助让我们的程序设计更加完善。
参考文献
[1]徐孝凯编著,《数据结构课程实验》,清华大学出版2002年第一版
[2]张乃笑编著,《数据结构与算法》,电子工业出版社2004年10月
[3]严蔚敏《数据结构》(C语言版)清华大学出版社
[4]李春葆《数据结构(C语言篇)—习题与解析》(修订版)清华大学出版2002年4月第一版
附录
源程序如下:
#include
#include//要用system函数要调用的头文件
#include//用getch()要调用的头文件
#include
#defineN50//义用N表示50叶节点数
#defineM2*N-1//用M表示节点总数当叶节点数位n时总节点数为2n-1
#defineMAXSIZE100
typedefstruct
{
chardata;//结点值
intweight;//权值
intparent;//双亲结点
intlchild;//左孩子结点
intrchild;//右孩子结点
}HTNode;
typedefstruct
{
charcd[N];//存放哈夫曼码
intstart;//从start开始读cd中的哈夫曼码
}HCode;
voidCreateHT(HTNodeht[],intn)//调用输入的数组ht[],和节点数n
{
inti,k,lnode,rnode;
intmin1,min2;
for(i=0;i<2*n-1;i++)
ht[i].parent=ht[i].lchild=ht[i].rchild=-1;//所有结点的相关域置初值-1
for(i=n;i<2*n-1;i++)//构造哈夫曼树
{
min1=min2=32767;//int的范围是-32768—32767
lnode=rnode=-1;//lnode和rnode记录最小权值的两个结点位置
for(k=0;k<=i-1;k++)
{
if(ht[k].parent==-1)//只在尚未构造二叉树的结点中查找
{
if(ht[k].weight{
min2=min1;rnode=lnode;
min1=ht[k].weight;lnode=k;
}
elseif(ht[k].weight{
min2=ht[k].weight;rnode=k;
}
}
}
ht[lnode].parent=i;ht[rnode].parent=i;//两个最小节点的父节点是i
ht[i].weight=ht[lnode].weight+ht[rnode].weight;//两个最小节点的父节点权值为两个最小节点权值之和
ht[i].lchild=lnode;ht[i].rchild=rnode;//父节点的左节点和右节点
}
}
voidCreateHCode(HTNodeht[],HCodehcd[],intn)
{
inti,f,c;
HCodehc;
for(i=0;i{
hc.start=n;c=i;
f=ht[i].parent;
while(f!
=-1)//循序直到树根结点结束循环
{
if(ht[f].lchild==c)//处理左孩子结点
hc.cd[hc.start--]='0';
else//处理右孩子结点
hc.cd[hc.start--]='1';
c=f;f=ht[f].parent;
}
hc.start++;//start指向哈夫曼编码hc.cd[]中最开始字符
hcd[i]=hc;
}
}
voidDispHCode(HTNodeht[],HCodehcd[],intn)//输出哈夫曼编码的列表
{
inti,k;
printf("输出哈夫曼编码:
\n");
for(i=0;i{
printf("%c:
\t",ht[i].data);
for(k=hcd[i].start;k<=n;k++)//输出所有data中数据的编码
{
printf("%c",hcd[i].cd[k]);
}
printf("\n");
}
}
voideditHCode(HTNodeht[],HCodehcd[],intn)//编码函数
{
charstring[MAXSIZE];
inti,j,k;
scanf("%s",string);//把要进行编码的字符串存入string数组中
printf("\n输出编码结果:
\n");
for(i=0;string[i]!
='#';i++)//#为终止标志
{
for(j=0;j{
if(string[i]==ht[j].data)//循环查找与输入字符相同的编号,相同的就输出这个字符的编码
{
for(k=hcd[j].start;k<=n;k++)
{
printf("%c",hcd[j].cd[k]);
}
break;//输出完成后跳出当前for循环
}
}
}
}
voiddeHCode(HTNodeht[],HCodehcd[],intn)//译码函数
{
charcode[MAXSIZE];
inti,j,l,k,m,x;
scanf("%s",code);//把要进行译码的字符串存入code数组中
while(code[0]!
='#')
for(i=0;i{
m=0;//m为想同编码个数的计数器
for(k=hcd[i].start,j=0;k<=n;k++,j++)//j为记录所存储这个字符的编码个数
{
if(code[j]==hcd[i].cd[k])//当有相同编码时m值加1
m++;
}
if(m==j)//当输入的字符串与所存储的编码字符串个数相等时则输出这个的data数据
{
printf("%c",ht[i].data);
for(x=0;code[x-1]!
='#';x++)//把已经使用过的code数组里的字符串删除
{
code[x]=code[x+j];
}
}
}
}
voidmain()
{
intn=26,i;
charorz,back,flag=1;
charstr[]={'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'};//初始化
intfnum[]={186,64,13,22,32,103,21,15,47,57,1,2,32,20,57,63,15,1,48,51,80,23,8,18,1,16};//初始化
HTNodeht[M];//建立结构体
HCodehcd[N];//建立结构体
for(i=0;i{
ht[i].data=str[i];
ht[i].weight=fnum[i];
}
while(flag)//菜单函数,当flag为0时跳出循环
{
printf("\n");
printf("**************************************");
printf("\n**A---------------显示编码**");
printf("\n**B---------------进行编码**");
printf("\n**C---------------进行译码**");
printf("\n**D---------------退出**\n");
printf("****************************************");
printf("\n");
printf("请输入选择的编号:
");
scanf("%c",&orz);
switch(orz)
{
case'a':
case'A':
system("cls");//清屏函数
CreateHT(ht,n);
CreateHCode(ht,hcd,n);
DispHCode