哈夫曼编码与译码报告要点.docx
《哈夫曼编码与译码报告要点.docx》由会员分享,可在线阅读,更多相关《哈夫曼编码与译码报告要点.docx(13页珍藏版)》请在冰豆网上搜索。
![哈夫曼编码与译码报告要点.docx](https://file1.bdocx.com/fileroot1/2023-1/4/eaf3edf2-99b0-4242-8e53-60a2c9df9d81/eaf3edf2-99b0-4242-8e53-60a2c9df9d811.gif)
哈夫曼编码与译码报告要点
一、设计思想
程序要求:
利用哈夫曼树对字符串进行编码,要求每个字符有自己唯一的编码。
将得到的一串字串译成0、1编码后存到一个文件夹中,然后再从这个文件夹中读出这串编码进行解码。
实现方法:
输入一串字符,要求字符的区间为大写的26个英文字母,将获得的串字符用计算权值的函数(jsquanzhi())进行字符统计,统计出现的字符种数以及每种字符出现的次数,将该种字符出现的次数作为它的权值。
将出现的字符的权值和该字符依次分别赋给两个结构体HT和HC,利用HT(节点)权值的大小建立哈夫曼树,首先用选择函数select()函数选择两个权值最小的字符作为叶子节点,创建一个新的节点作为这两个叶节点的父节点,被选中的节点给他的HT[i].parent赋值是他下次不再被选中,父节点的权值为,子节点的权值之和。
然后将该将父节点放入筛选区中,再进行选择(被选过的不再被使用),直到所有的节点都被使用,这样一个哈夫曼树就被建立了。
根据每个字符在哈夫曼书中的位置来编译每个字符的0、1密文代码,从叶节点判断该叶节点是其父节点的左右字左字为‘0’,右子为‘1’,在判断父节点是上级父节点的左右子直至根节点,将生成的0、1字符串按所表示的字符倒序存入HC相应的字符的bins[]数组。
重新一个一个字符的读取输入的字符串,按照字符出现的顺序将它转为0、1代码并存到一个txt文件夹中去。
解码时从文件夹中,一个一个字符的读出那串0、1代码赋给一个临时字符串cd[],用该字符串与每个字符的HC[i].bins密文比较,直到与一个字符的密文相同时,译出该字符,将字符存放在临时字符数组tempstr[]中,清空临时字符串继续读取0、1代码进行翻译,直至文件密文结束为止。
于是就得到了原先被加密的那串字符串。
二、算法流程图
三、源代码
//hafuman.cpp:
定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include"stdlib.h"
#include"string.h"
#include"stdio.h"
#definen100//叶节点的个数小等于n
#definem2*n-1//总结点的个数为m=2*n-1
intnum;//定义一个全局变量用于存放字符种类个数
typedefstruct//结构体用于存放树节点包括节点的父节点、左子、右子以及权值
{intweight;
intparent,lchild,rchild;
}HTNode;
typedefHTNodeHafumanTree[m+1];//重命名HTNode
typedefstruct//结构体由于存放每个字符的密文和长度
{charch;
charbits[10];
intlen;
}CodeNode;
typedefCodeNodeHafumanCode[n+1];//重命名CodeNode
int_tmain(intargc,_TCHAR*argv[])
{intquan[27];//声明一个数组用以存放26字符的权值
chargetstr[300],str[27];
//声明两个字符串数组一个用于存输入一个由于存放输入中含有的字符
char*s;//声明一个char型指针用于指向字符HafumanTreeHT;//声明m+1个树节点
HafumanCodeHC;//声明n+1个code
intjisuanquan(char*s,intquan[],charstr[]);//声明需要调用的函数
voidgjhafumantree(HafumanTreeHT,HafumanCodeHC,intquan[],charstr[]);
voidHafumanencode(HafumanTreeHT,HafumanCodeHC);
voidcoding(HafumanCodeHC,char*str);
char*decode(HafumanCodeHC);
printf("请输入要加密的字符串:
\n");
gets(getstr);//获得输入的字符串
num=jisuanquan(getstr,quan,str);//统计字符串中含有字符种类个数
//printf("%d\n",num);
gjhafumantree(HT,HC,quan,str);//根据字符权值构建哈夫曼树
Hafumanencode(HT,HC);//根据哈夫曼树确定每个字符的code
coding(HC,getstr);//将字符串译码存入文件夹
s=decode(HC);//将暗文解码
printf("解密为:
\n");
printf("%s\n",s);
system("pause");
return0;
}
//函数
intjisuanquan(char*s,intquan[],charstr[])//计算字符串中字符权值
{char*p;
inti,j,k,quantemp[27];
for(i=1;i<27;i++)//将所有字符的权值赋成0
{quantemp[i]=0;}
for(p=s;*p!
='\0';p++)//判断字符串是否结束
{if(*p>='A'&&*p<='Z')//判断字符是否为26字母
{k=*p-64;//看是26个字符中的哪个字符
quantemp[k]++;//字符权值加1
}
}
j=0;
for(i=1,j=0;i<27;i++)
{if(quantemp[i]!
=0)
{j++;//用于统计字符种类个数
str[j]=i+64;//str按字母表顺序存储出现过的字符
quan[j]=quantemp[i];
}
}
returnj;
}
voidselect(HafumanTreeHT,intk,int*s1,int*s2)//选择权值最小的两个
{inti,j;
intmin1=9999;//声明一个int类型的数值min1,赋个较大的输给它
for(i=1;i<=k;i++)//选择权值最小的一个节点(且该节点无父节点)
{if((HT[i].weight{j=i;min1=HT[i].weight;}
}
*s1=j;
min1=9999;
for(i=1;i<=k;i++)
{if((HT[i].weight=*s1))
//选择权值最小的一个节点(且该节点无父节点)
{j=i;min1=HT[i].weight;}
}
*s2=j;
}
voidgjhafumantree(HafumanTreeHT,HafumanCodeHC,intquan[],charstr[])//构建哈夫曼树
{inti,s1,s2;
for(i=1;i<2*num-1;i++)//将所有的节点赋空
{HT[i].lchild=0;HT[i].rchild=0;
HT[i].parent=0;HT[i].weight=0;
}
for(i=1;i<=num;i++)//将num个字符的权值赋给num叶节点
{HT[i].weight=quan[i];
}
for(i=1;i<=num;i++)//将num个字符赋给codenode
{HC[i].ch=str[i];
}
i=1;
while(i<=num)
{printf("===%c===%d===\n",HC[i].ch,quan[i]);i++;}//输出每个字符的及其权值
for(i=num+1;i<=2*num-1;i++)
{select(HT,i-1,&s1,&s2);//选择两个权值最小的叶节点
HT[s1].parent=i;HT[s2].parent=i;//两个节点指向同一父节点
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
//父节点的权值为子节点相加(父节点继续放入选择区)
}
}
voidHafumanencode(HafumanTreeHT,HafumanCodeHC)
{
intc,p,i;
charcd[n];//临时数组用于记录字符在哈夫曼树的位置
intstart;
cd[num]='\0';//给cd赋个结束符
for(i=1;i<=num;i++)
{start=num;
c=i;
while((p=HT[c].parent)>0)//根据节点是其父节点的左右子来记录它的位置
{cd[--start]=(HT[p].lchild==c)?
'0':
'1';
c=p;//将父节点转为子节点
}
strcpy(HC[i].bits,&cd[start]);//将得到的0、1字串存入结构体HC
printf("%c:
%s\n",HC[i].ch,HC[i].bits);
HC[i].len=num-start;//求每个字符0、1编码长度
}
}
voidcoding(HafumanCodeHC,char*str)//根据哈夫曼树确定每个字符的0、1代码code
{inti,j;
FILE*fp;//声明一个文件夹指针
fp=fopen("codefile.txt","w");//打开文件夹codefile
printf("密文为:
\n");
while(*str)//字符串未结束时
{for(i=1;i<=num;i++)
{if(HC[i].ch==*str)//判断字符是否在Codenode中存在
{for(j=0;j{fputc(HC[i].bits[j],fp);}
printf("%s",HC[i].bits);
break;
}
}
str++;//字符后移
}
printf("\n");
fclose(fp);
}
char*decode(HafumanCodeHC)
{FILE*fp;
chartempstr[9999];
char*p;
staticcharcd[n+1];//char型数组用于存放从文件夹中读取的1、0代码
inti,j,k=0,jsjs;
fp=fopen("codefile.txt","r");
while(!
feof(fp))//当文件夹读取没有结束
{jsjs=0;//判读一个字符是否译码结束
for(i=0;(ifeof(fp));i++)//当一个字符未译完并且文件未读取结束
{cd[i]='';cd[i+1]='\0';//让cd[]赋成空格
cd[i]=fgetc(fp);//读取文件夹中的一个字符
for(j=1;j<=num;j++)
{if(strcmp(HC[j].bits,cd)==0)//看cd[]的字符串是否等于Codenode中的某个密文
{tempstr[k]=HC[j].ch;jsjs=1;printf("=%s=",HC[j].bits);k++;break;}
//将译出的字符赋给临时字符串tempstr[],标记一个字符译码结束jsjs赋1,跳出循环
}
}
}
tempstr[k]='\0';//赋给临时字符串一个结束标志
p=tempstr;//char型指针指向临时字符串
returnp;
}
四、运行结果
图5哈夫曼编码与译码运行结果图
五、遇到的问题及解决
这部分我主要遇到了如下两个问题,其内容与解决方法如下所列:
●当我写完程序,输入一段字符串让程序进行译码和解码是出现了一个问题,译出的结果不是想要的结果,但结果又有一定规律,就是结果中的字符都在明文中出现过,可是字符数量明显比明文中的要少很多。
首先我认为为题可能出现在我将明文加密后的存储阶段,于是我在程序将0、1代码存入codeflie.txt文件前,先将临时存储字符串数组内容输出,结果发现没有问题,于是我又在,译码阶段将从codeflie.txt读出的内容逐字输出发现结果和存入的一样。
输入和输出都一样。
问题不是出现在存储上,于是我想到问题可能出现在,我将0、1编码转成明文的时。
于是我认真的看了看我的decode函数,添加了些辅助输出,发现有写编码不是原先的一个字符的译文,有的是两个字符的译文译成了一个字符。
于是我又输进一段字符将暗文带入函数计算,发现问题出在但一出一个字符后,我的一个循环并没有终止,而是继续向cd[]数组中添加,1和0;直至cd[]的长度超过num时才结束,而下次调用该循环式,前面会有几个1、0没有被编译。
所以导致了以上的错误,于是我在函数中声明了一个变量jsjs(计算结束),用它在译完一个字符是让函数跳出循环。
于是问题得到了解决。
●第二个问题再之前就出现了只不过他和前一个问题没有多大的联系,就是在输出明文结果是字符串的结尾变成了:
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫…………。
一开始我认为他和第一个问题是一个问题,是存储或者译码出错了,但是但第一个问题解决后他依然存在。
于是我才确定这是另外的一个问题。
根据以晚的经验出现这种情况的原因就几种,存储出错或者输出形式不对,或者输出的位置不对。
而第二种情况可以排除,于是我前后看了看程序中的数组的存储和输出。
发现存储形式没有问题,于是我想到了第一个问题解决后输出的结果是在正确解码后面加一串乱码。
会不会是输出了一段没有赋值区域内的内容,于是我看了看存储输出内容的临时数组发现我声明时声明的长度为300,而一般用不了那么多,而我输出是将str[]按照字符串形式输出的也就是说,输出结果的前一段时我要的内容而后一段是系统原先的东西,但是也跟着输出了,于是我在str[]中添加了’\0’结束标志。
这样就不会输出我不要的部分了。
后来但我输入更长的一段字符串是后没会出现一个字的汉字,于是我又将str的长度加长,是它尽可能存储跟唱的字符串。
六、心得体会
通过对上面程序的编写让我积累了一些编程的经验,比如在你编写代码是首先你应在大脑内先构思好你的程序,并在纸上写下你的思路很实现步骤,而不是急于动手编写。
有些事没想好就急于容易出现一些逻辑上的错误,最后实现不了又必须重来。
其次是数据类型的处理,不能将字符型数据存其他类型的数据,计算机是不识别的,写程序是要严格规范自己对不同类型数据存储和使用,还有就是当一个数据不再被使用时或下次你要用它来存放其他数值时要记得提前赋空,以免数据出错。
还有就是在程序中如果你要重复某些步骤时你可以考虑在主程序外建立一个函数来实现这些步骤,这样在主程序中通过调用这个函数来实现这些步骤,这样可以避免编写同样的代码,并简化你的程序代码。
除此之外就是大括号{}的一定要对其,不然一旦代码长度变长,会出现一堆的括号,你可能看的眼花缭乱不知道哪个与哪个是一对,最后影响你的代码检查。