哈夫曼编码与译码.docx
《哈夫曼编码与译码.docx》由会员分享,可在线阅读,更多相关《哈夫曼编码与译码.docx(32页珍藏版)》请在冰豆网上搜索。
哈夫曼编码与译码
哈夫曼编码与译码
用哈夫曼树对字符串进行编码译码
一、设计思想
程序要求:
要求每个字符有自己唯一的编码。
将得到的一串利用哈夫曼树对字符串进行编
码,
字串译成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
代码进行翻译,直至文件密文结束为止。
于是就得到了原先被加密的那串字符串。
-1-
用哈夫曼树对字符串进行编码译码
二、算法流程图
开始
获得输入的字符串getstr[],i=0。
判断getstr[i]是否i++;为26个大写字母否
是
k=getstr[i]-64;quantemp[k]++(quantemp为int型数
组开始值都为0)i++;
i=1,j=0
否
i<27?
是
结束是quantemp[i]=i++;
0?
否
j++;str[j]=i+64;
quan[j]=quantemp[i]
;
图1计算字符权值及字符种类算法
说明:
将的的字符串进行字符种类级每种字符出现频率的统计
-2-
用哈夫曼树对字符串进行编码译码
开始
将所有的几点的父节
点和子节点权值赋成0
i=1;(num为字
符的种类)
HT[i].weight=quan[i]i<=num
是否
i=num+1
否
i<=2*num-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].weigh
t;
i++
图2构建哈夫曼树算法
说明:
利用选择排序,选择节点权值最小的两个节点,构建一个子树,
将该树的根节点再放入选择区,重复该操作,直至用完所有节点完成
哈夫曼数的搭建。
-3-
用哈夫曼树对字符串进行编码译码
开始
将str[i]的值依次赋给HC[i].chi=0;cd[num]='\0';
i<=num;
是
start=num;c=i
否
i++
HT[c].parent
strcpy(HC[i].bits,>0否&cd[start]);是
HC[i].len=num-start;cd[--start]=(HT[p].
lchild==c)?
'0':
'1';
c=HT[c].parent;打开codefile.txt文件,获得
字符串str指针,i=0;
否
i<=num;
是结束否否
HC[i].ch=*str?
i++
是
将HC[i].bits[j]存进文件
图3利用哈夫曼树加密算法
说明:
利用每个字符在哈夫曼树中的位子,得到每个字符的0、1密文编
码。
再将字符串按字符密文进行编译,然后存入文件夹中。
-4-
用哈夫曼树对字符串进行编码译码
开始
cjs=0,i=0打开文件夹codefile.txt,
!
feof(fp)
结束
(i
&&(!
feof(fp)
cd[i]='';cd[i+1]='\0';
cd[i]=fgetc(fp);j=1;
j<=numi++
j++
strcmp(HC[j]
.bits,cd)=0
str[k]=HC[j].ch;cjs=1;
k++;str[k]='\0';break;
图4解密算法
说明:
从文件夹中读出密文,和HC[i].bis中的密文进行比较译出
字符,存入临时数组。
待译码结束后,输出字符串。
-5-
用哈夫曼树对字符串进行编码译码
三、源代码
//hafuman.cpp:
定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include"stdlib.h"
#include"string.h"
#include"stdio.h"
#definen100//叶节点的个数小等于n#definem2*n-1//总结点的个数为
m=2*n-1intnum;//定义一个全局变量用于存放字符种类个数
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);void
coding(HafumanCodeHC,char*str);char*decode(HafumanCodeHC);-6-
用哈夫曼树对字符串进行编码译码
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;
}
-7-
用哈夫曼树对字符串进行编码译码
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[],char
str[])//构建哈夫曼树{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;
//父节点的权值为子节点相加(父节点继续放入选择区)
}
}
-8-
用哈夫曼树对字符串进行编码译码
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");//
打开
文件夹
codefileprintf("
密文为:
\n");
while(*str)//字符串未结束时
{for(i=1;i<=num;i++)
{if(HC[i].ch==*str)//
判断字符是否在
Codenode中存在
{for(j=0;j将codenode中该字符的
1、0代码存入文件夹
{fputc(HC[i].bits[j],fp);}
printf("%s",HC[i].bits);
break;
}
}
str++;//
}
printf("\n");
fclose(fp);
}
字符后移
-9-
用哈夫曼树对字符串进行编码译码
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++)
{f(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;
}
-10-
用哈夫曼树对字符串进行编码译码
四、运行结果
图5哈夫曼编码与译码运行结果图
-11-
用哈夫曼树对字符串进行编码译码
五、遇到的问题及解决
这部分我主要遇到了如下两个问题,其内容与解决方法如下所列:
当我写完程序,输入一段字符串让程序进行译码和解码是出现了一个问题,译出的结果不是想要的结果,但结果又有一定规律,就是结果中的字符都在明文中出现过,可是字符数量明显比明文中的要少很多。
首先我认为为题可能出现在我将明文加密后的存储阶段,于是我在程序将0、1
代码存入codeflie.txt文件前,先将临时存储字符串数组内容输出,结果发现没
有问题,于是我又在,译码阶段将从codeflie.txt读出的内容逐字输出发现结果
和存入的一样。
输入和输出都一样。
问题不是出现在存储上,于是我想到问题可能
出现在,我将0、1编码转成明文的时。
于是我认真的看了看我的decode函数,添
加了些辅助输出,发现有写编码不是原先的一个字符的译文,有的是两个字符的译
文译成了一个字符。
于是我又输进一段字符将暗文带入函数计算,发现问题出在但
一出一个字符后,我的一个循环并没有终止,而是继续向
cd[]
数组中添加,
1和
0;
直至
cd[]
的长度超过
num时才结束,而下次调用该循环式,前面会有几个
1、0没
有被编译。
所以导致了以上的错误,于是我在函数中声明了一个变量
jsjs(
计算结
束),用它在译完一个字符是让函数跳出循环。
于是问题得到了解决。
第二个问题再之前就出现了只不过他和前一个问题没有多大的联系,就是在输出明文结果是字符串的结尾变成了:
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫
烫。
一开始我认为他和第一个问题是一个问题,是存储或者译码出错了,但是但第一个问题解决后他依然存在。
于是我才确定这是另外的一个问题。
根据以晚的经验出现这种情况的原因就几种,存储出错或者输出形式不对,或者输出的位置不对。
而第二种情况可以排除,于是我前后看了看程序中的数组的存储和输出。
发现存储形式没有问题,于是我想到了第一个问题解决后输出的结果是在正确解码后面加一串乱码。
会不会是输出了一段没有赋值区域内的内容,于是我看了看存储输出内容的临时数组发现我声明时声明的长度为300,而一般用不了那么多,而我输出是将
str[]按照字符串形式输出的也就是说,输出结果的前一段时我要的内容而后一段
是系统原先的东西,但是也跟着输出了,于是我在str[]中添加了’0’结束标
志。
这样就不会输出我不要的部分了。
后来但我输入更长的一段字符串是后没会出
现一个字的汉字,于是我又将str的长度加长,是它尽可能存储跟唱的字符串。
-12-
用哈夫曼树对字符串进行编码译码
六、心得体会
通过对上面程序的编写让我积累了一些编程的经验,比如在你编写代码是首先你应在大脑内先构思好你的程序,并在纸上写下你的思路很实现步骤,而不是急于动手编写。
有些事没想好就急于容易出现一些逻辑上的错误,最后实现不了又必须重来。
其次是数据类型的处理,不能将字符型数据存其他类型的数据,计算机是不识别的,写程序是要严格规范自己对不同类型数据存储和使用,还有就是当一个数据不再被使用时或下次你要用它来存放其他数值时要记得提前赋空,以免数据出错。
还有就是在程序中如果你要重复某些步骤时你可以考虑在主程序外建立一个函数来实现这些步骤,这样在主程序中通过调用这个函数来实现这些步骤,这样可以
避免编写同样的代码,并简化你的程序代码。
除此之外就是大括号{}的一定要对其,不然一旦代码长度变长,会出现一堆的括号,你可能看的眼花缭乱不知道哪个与哪个是一对,最后影响你的代码检查。
-13-