数据结构实习哈夫曼编码文档格式.docx
《数据结构实习哈夫曼编码文档格式.docx》由会员分享,可在线阅读,更多相关《数据结构实习哈夫曼编码文档格式.docx(27页珍藏版)》请在冰豆网上搜索。
4本次作业心得................................................................24
1.题目
设二叉树以二叉链表的形式保存,T为指向根结点的指针。
试完成以下功能:
1、建立二叉树:
从键盘输入各结点的值,可参照二叉树的顺序存储方式。
例如输入“a,b,c,,d”表示结点a是根,b和c是a的两个孩子,b仅有右孩子d。
2、统计T中叶结点的个数。
3、统计T中度为2的结点的个数。
4、求树T的高度。
5、判断T中是否有度为1的结点(即按照国外教材的定义,是否为满树)。
在此基础上,实现Huffman编码及译码,达到以下两个目标:
1、掌握二叉树的二叉链表存贮结构。
2、掌握Huffman算法。
2.要求
使用文件保存初始的文本数据及最终的结果。
●文件名为inputfile1.txt的文件保存的是一段英文短文;
●文件名为inputfile2.txt的文件保存01形式的编码段;
●文件名为outputfile1.txt的文件保存各字符的出现次数和对应的编码;
●文件名为outputfile2.txt的文件保存对应于inputfile2.txt的译码结果。
统计inputfile1.txt中各字符的出现频率,并据此构造Huffman树,编制Huffman编码;
根据已经得到的编码,对01形式的编码段进行译码。
具体的要求:
1.将给定字符文件编码:
生成编码,输出每个字符出现的次数和编码;
2.将给定编码文件译码:
生成字符,输出编码及其对应字符。
3.程序实现
3.1程序运行及编译环境
程序是用VisualStudio2010即VS10.0编译的。
可以在windows系列的操作系统上运行。
3.2程序描述
该程序主要用于实现二叉树的二叉链表保存并实现Huffman编码以及译码。
其流程如下:
A).程序读取输入文件,并生成相应的数据结构
a.用数组来声明并存储树,完成初始化的工作
b.读入文本内容,并统计每个字符的频率
c.根据每个字母的权重,对森林里每棵树的相应参数重新赋值
d.将森林合并为树
B).输出相关信息
a.在文本里依次输出字符出现的次数,频率以及编码
B.在控制台输出统计的字母频率(其实这个不是必要的)
C).编码以及译码
a.在统计好的字符出现次数的基础上编码
b.编码前的两个文件比较
c.对编码的文件译码
d.译码后的两个文件比较
D).完成
3.3实现功能
3.3.1子功能模块1
读入文本,统计频率。
//开始读入文件
while(fin1.get(c)){//读入文件
let[c].weight++;
//统计每个字符的权重
CharCount++;
if(c<
0||c>
127){
cout<
<
"
打开文件在一以下位置出现错误:
;
fin1.get(c);
fin1.get(c);
for(inti=0;
i<
TIPS;
i++){fin1.get(c);
cout<
c;
}
的前面"
temp_char<
的后面"
endl;
可能原因有:
①文本编码为UTF-8,Unicode,Unicodebigendian等非ANSI编码;
"
②或者文本含有中文字符或者全角字符"
endl<
请核查!
return1;
}
if((c>
='
a'
&
c<
z'
)||(c>
A'
Z'
))
LetterCount++;
for(inti=0;
i++)
temp_char[i]=temp_char[i+1];
temp_char[TIPS]=c;
fin1.close();
//读入完成
3.3.1.1输入项
输入项有:
fin1:
文件输入流
3.3.1.2输出项
let[c].weight;
//每个字符的出现次数
CharCount;
//所有字符出现次数总和
//文件流读入的每个字符
temp_char;
//保存当前读到字符的前几个字符,以便于在含有Unicode,UTF-8等编码的字符时,及时找出出错字符位置
3.3.1.3数据结构的定义
3.3.1.3.1全局数据结构
全局数据结构有:
//用一个结构来存储字符,统计的权重,以及该字母在哈夫曼树里面的偏移,这样可以快速定位到该字母的结点
typedefstructLet{
charletter;
//字符
intoffset;
//字母在哈夫曼树里面的偏移
intweight;
//字符的权重
doublefreq;
//freq表示在所有字符中的频率
doubleletterFreq;
//表示字母在所有字母中的频率
};
typedefstructHuffNode{
//letter表示结点的字符
//weight表示权重
HuffNode*left;
//左孩子
HuffNode*right;
//右孩子
boolmerge;
//merge表示能否合并,如果两棵树合并后,就不可以合并了
boolcode[MAXCODELENGTH];
//0表示左边,1表示右边
intValidCode;
//ValidCode表示前多少位的编码是有效的
classHuffTree{
HuffNode*root;
//树的根节点
public:
HuffTree(charletter,intweight);
HuffTree();
HuffNode*getRoot();
voidsetRoot(HuffNode*Root);
HuffTree*HuffTreeMerge(HuffTree*Tree1,HuffTree*Tree2);
HuffTree*CreateHuffTree(HuffTreeHN[],intCOUNT);
voidMidOrder(HuffNode*node);
voidPreOrder(HuffNode*node);
voidHuffCode(HuffNode*root);
}
3.3.1.32局部数据结构
无
3.3.1.4算法及程序说明
本程序中重要的算法思想是:
给出了一种编码的时候不同的遍历方法。
把树中所有的结点都对应到一个整数(不同的结点,对应不同的整数),然后按照整数从小到大的顺序,依次找到其结点并且访问,编码。
这个对应的关系式这样的:
一个整数,例如10,那么它的二进制表示为1010B,于是,把最高位1去掉,剩下的是010B,然后这个010B对应的是从根节点的左子树的右子树的左子树对应的结点,(就是0表示左子树,1表示右子树)并且根据这个二进制数,可以很快的找到这个结点,而且每次都是从根节点开始,没有系统栈的开销。
不同的结点,对应不同的整数是显然的,因为从根节点到达不同的结点的次序是不同的,因此这些次序对应的二进制代码也不一样,在其前面加一项1以后,还是不一样的。
为此,需要用到一下两个函数作为基础
A).本程序最重要的是这几个函数:
1.voidConvert2Bin(intnum,boola[],int&
idx);
把一个十进制数转化为二进制数
num:
要转化的十进制数;
boola[]:
为节省空间,采用bool型来存储二进制的每一位;
idx表示
代码如下:
voidConvert2Bin(intnum,boola[],int&
idx){//把一个整数num转化为二进制的形式,存放在数组a[]里面,idx存放输出到数组的哪一项
inti=0;
booltemp;
while(num>
1){//除2取余,得到逆序的二进制序列
a[i++]=num%2;
num/=2;
a[i]=num;
idx=i;
for(intj=0;
j<
(idx+1)/2;
j++)//把逆序的变为正序的
{
temp=a[j];
a[j]=a[idx-j];
a[idx-j]=temp;
2.voidCodingOrder(intnum,boola[],int&
由num决定编码二叉树每个结点的顺序num表示数的序号;
a[]这个数组存遍历的序列;
idx表示这个数组的前多少项有效
voidCodingOrder(intnum,boola[],int&
idx){//
Convert2Bin(num,a,idx);
for(inti=0;
idx;
a[i]=a[i+1];
idx--;
B).其他用到的算法
森林合并为树的算法:
每两棵树合并成一棵新树,一直合并直到只剩下一棵树,森林就变成树了。
Huffman编码算法:
从1到MAXSIZE*MAXSIZE的所有整数都有一个二进制形式,然后取
除第一位以外的所有二进制位作为遍历的顺序,如果遇到根节点
就把那个二进制的顺序作为该节点的编码,遇到合并以后的结点
继续往下走,如果编码的位数更多,只要把最大遍历整数调大即可
算法代码如下:
voidHuffCode(HuffNode*root){
boolorder[MAXCODELENGTH];
//存放编码的数组
intcount=0;
//count表示该数组的前多少位表示编码
HuffNode*temp=newHuffNode;
//用于遍历
for(inti=1;
MAXSIZE*MAXSIZE;
i++){
temp=root;
//每个数都是从头开始找
CodingOrder(i,order,count);
//得到这个数对应的遍历顺序
for(intj=0;
=count;
j++){
switch(order[j]){
casetrue:
//true时,右走
temp=temp->
right;
break;
casefalse:
//false时,左走
left;
default:
cout<
Error!
//调试的时候,快速定位出错点
}
if(temp->
letter){//到达一个有字母的结点,这时候就可以得到编码
temp->
ValidCode=j;
for(intk=0;
k<
=temp->
ValidCode;
k++)
temp->
code[k]=order[k];
break;
}
}
C).体会
算法并不是什么代码,也不是什么规则,方法只要能用有限的步骤实现相应的功能,这就是一个算法。
由于开始时,我死按着常规的顺序去遍历树,想去得到每个节点的编码,发现递归遍历代码容易出错,非递归遍历代码太复杂,以至于我写的每一次遍历都得不到正确结果,所以后来受第一个习题的影响,用了第一个习题里面的思想。
3.3.2子功能模块2
用于实现两棵树合并成一棵新树
Tree1,Tree2,分别表示两棵树指针,返回两棵树合并后形成的一颗新树
HuffTree*HuffTreeMerge(HuffTree*Tree1,HuffTree*Tree2){
HuffTree*Tree=newHuffTree;
if(Tree1->
root->
merge==false||Tree2->
merge==false)//两棵树均不可合并时,给出错误提示
{
Cannotmergeintooneroot!
returnTree1;
Tree->
weight=Tree1->
weight+Tree2->
weight;
//新树的权重是原来两棵树的权重之和
merge=true;
//新树标记为可以合并
Tree1->
merge=false;
//原来那两棵树均不可合并
Tree2->
left=Tree1->
root;
//Tree1为左子树
right=Tree2->
//Tree为右子树
returnTree;
3.3.3子功能模块3
HuffTree*CreateHuffTree(HuffTreeHN[],intCOUNT);
HN[]表示分立的树;
COUNT表示树的棵树,返回形成的那棵哈夫曼树的根;
实现方法:
先取出可以合并的两棵树,然后往后找,发现权重比那两棵小的就取权重小树
直到最后,然后,两棵树合二为一,合成的新树依次放在原来数组的后面,直到原来
所有的树都合并完毕。
HuffTree*CreateHuffTree(HuffTreeHN[],intCOUNT){//count表示可以用来合并成为哈夫曼树的结点个数
HuffTree*minTree1;
HuffTree*minTree2;
HuffTree*temp;
inti=0;
intcount=COUNT;
intappend=COUNT;
while(count>
1)//还没有合并完毕
for(i=0;
2*COUNT-1;
i++)//找第一课可以合并的树
if((HN+i)->
merge==true&
(HN+i)->
weight!
=0)
{
minTree1=HN+i;
i++;
break;
for(;
i++)//找第二课可以合并的树
minTree2=HN+i;
if(minTree1->
weight>
minTree2->
weight)//使得minTree1总是小于minTree2;
temp=minTree1;
minTree1=minTree2;
minTree2=temp;
for(;
i++)//后面要是有可以合并的权重比之更小的树就取那棵树用于合并
if((HN+i)->
{
weight<
minTree1->
weight)
minTree2=minTree1;
minTree1=HN+i;
else
minTree2->
root=HuffTreeMerge(minTree1,minTree2)->
//两棵树合并成为一棵树
HN[append++].root=Tree->
//新合成的树置于原来数组后面
count--;
3.3.4子功能模块4
函数原型:
voidMidOrder(HuffNode*node);
函数参数:
树的根节点
实现方法:
递归,中序遍历
代码:
voidMidOrder(HuffNode*node){//中序递归遍历,并且输出编码
if(node)
MidOrder(node->
left);
if(node->
letter){
cout<
char(node->
letter)<
weight:
node->
weight<
Code:
=node->
code[i];
right);
3.4运行结果
A).程序基本功能的实现:
图1控制台输出每个字符的权重以及编码
图2控制台输出每个字符的权重以及编码
图3编码的文件inputfile1.txt以及解码后的文件outputfile2.txt文件大小均为19KB
图4编码的文件inputfile1.txt
图5解码后的文件outputfile2.txt
由于这样的题目比较有意思,而且输入的文本是大学两年来写过的所有英语作文,这些数据是在十几篇英语作文里面的含空格,回车等一切标点的19053个字符,1019个互不相同的单词的数据基础上得出的。
所以,除了实现以上功能外,我还特地统计了自己写的作文的一些统计特性,比如每个英文字符在所有英文字符的频率,发现还是e的频率最高,当然了,程序中e和E算作不同的字母。
B).附加功能:
图6统计所有字符的频率,如果是英文字符,还算其在所有英文字符这个群体里的频率。
这在一定程度上反映了语言的特性,利用一个更加大的更具有普遍性的语料库,可以统计所有字母的频率,有一种古老破解密码的方法就是基于这种大语料库下字符频率的,虽然密文不是严格按照这个频率分布的,但是频率最大的几个字母是不变的,这就为破解密码提供了可能,当然了,现代密码学是建立在数论基础上的公开密钥的方法,密文不再具有统计特性。
扯得有点远,其