北邮哈夫曼数据结构实验报告.docx
- 文档编号:5427471
- 上传时间:2022-12-16
- 格式:DOCX
- 页数:20
- 大小:376.15KB
北邮哈夫曼数据结构实验报告.docx
《北邮哈夫曼数据结构实验报告.docx》由会员分享,可在线阅读,更多相关《北邮哈夫曼数据结构实验报告.docx(20页珍藏版)》请在冰豆网上搜索。
北邮哈夫曼数据结构实验报告
数据结构实验报告
实验名称:
实验4Huffman树
学生姓名:
班级:
班内序号:
学号:
日期:
2015年1月5日
1.实验要求
利用二叉树结构实现哈夫曼编/解码器。
基本要求:
1、初始化(Init):
能够对输入的任意长度的字符串s进行统计,统计每个字符的频度,并建立哈夫曼树
2、建立编码表(CreateTable):
利用已经建好的哈夫曼树进行编码,并将每个字符的编码输出。
3、编码(Encoding):
根据编码表对输入的字符串进行编码,并将编码后的字符串输出。
4、译码(Decoding):
利用已经建好的哈夫曼树对编码后的字符串进行译码,并输出译码结果。
5、打印(Print):
以直观的方式打印哈夫曼树(选作)
6、计算输入的字符串编码前和编码后的长度,并进行分析,讨论赫夫曼编码的压缩效果。
7、可采用二进制编码方式(选作)
测试数据:
IlovedataStructure,IloveComputer。
IwilltrymybesttostudydataStructure.
提示:
1、用户界面可以设计为“菜单”方式:
能够进行交互。
2、根据输入的字符串中每个字符出现的次数统计频度,对没有出现的字符一律不用编码。
2.程序分析
2.1存储结构
1)Huffman树的结点结构structhnode
{
chardaibiao;//结点代表的字符
intweight;//结点的权重
intparent;//双亲指针
intlchild;//左孩子指针
intrchild;//右孩子指针
};
2)编码表结点结构(如右图2-6所示)
20
Structhcode
0
1
data
code
{
A
11
0
Z
100
chardata;//存储字符
0
1
1
C
101
charcode[100]
5
B
2
B
11
};
0
1
3
A
0
Z
C
图2-6Huffman树编码结构
3)Huffman类结构
classHuffman
{
private:
HNode*HTree;
//Huffman树
HCode*HCodeTable;
//Huffman编码表
charstr[1024];
//输入的原始字符串
charleaf[256];
//叶子节点对应的字符
inta[256];
//记录每个出现字符的个数
public:
intn;
//叶子节点数
voidinit();
//初始化
voidCreateHTree();
//创建huffman树
voidSelectMin(int&x,int&y,ints,inte);
voidCreateCodeTable();
//创建编码表
voidEncode(char*d);
//编码
voidDecode(char*s,char*d);
//解码
voidprint(inti,intm);
//打印Huffman树
~Huffman();
}
2.2关键算法分析
1统计输入的字符串中字符频率:
用字符串记录用户输入的原始数据,统计一共出现了多少种字符,各出现了多少次即为字符的权值。
对未出现的字符不予统计编码。
将统计的叶子节点编制成数组。
为创建哈夫曼树作准备。
[1]用字符串str接收用户输入的数据
[2]用动态字符数组存储字符出现的个数
[3]如果是不同的字符,用k来记录,k+1,统计所有不同字符的个数
[4]创建哈夫曼树Htree,哈夫曼编码表HcodeTable
[5]初始化哈夫曼编码表
[6]初始化哈夫曼树
[6.1]标记叶子节点,无左子树右子树及父节点
[7]释放动态字符数组
2.voidhuffman:
:
Init(char*s)
3.{
4.intn=0;
5.while(*(s+n)!
='\0')//统计字符串字符数
6.{
7.n++;
8.}
9.char*m=newchar[n];//动态字符数组存储字符串
10.for(inti=0;i 11.{ 12.m[i]=*(s+i); 13.} 14.chartemp;//定义中间变量temp 15.for(i=0;i 16.{ 17.for(intj=0;j 18.{ 19.if(m[j]>m[j+1]) 20.{ 21.temp=m[j]; 22.m[j]=m[j+1]; 23.m[j+1]=temp; 24.} 25.} 26.} 27.intk=1;//定义k为1,因为统计次序从1位置开始 28.for(i=1;i 29.{ 30.if(m[i]! =m[i-1]) 31.{ 32.k++; 33.} 34.} 35.htree=newhnode[2*k-1];//根据需要编码的字符个数创建新树 36.hn=2*k-1;//数组的规模为2K-1,一棵树有n个结点共有2n-1个结点 37.intl=0;//统计不同字符出现的频度 38.k=0;//哈夫曼数组下标 39.temp=m[0];//从第一个字符开始记录 40.for(i=0;i 41.{ 42.if(m[i]==temp) 43.{ 44.l++;//统计出现次数 45.if(i==n-1) 46.htree[k].weight=l; 47.} 48.else 49.{ 50.htree[k].weight=l; 51.htree[k].daibiao=temp;//记录结点代表的字符 52.temp=m[i];//迭代,记录下一个字符 53.k++;//哈夫曼数组下标进1 54.l=1;//重新设置权值时必须为1 55.htree[k].daibiao=temp;//下一个为 56.htree[k].weight=l; 57.} 58.} 59.delete[]m;//释放动态内存空间 60.createhuffman();//创建哈夫曼树 61.createcodetable();//创建哈夫曼编码表 62.}}创建Huffman树 主要思想: 从所有未使用过的权值表中选择两个最小的权值,可以有多种方法,比如一次选择一个最小的,选择2遍;或者进行迭代,一次选择出两个。 显然,后者的时间效率较高,因此我们采用后者进行实现。 迭代选择两个最小值得基本思想是: 1、从权值表HTree[s..e]中选取第一个未使用结点下标为x,并设y=x; 2、从剩下的未使用的权值中依次遍历 若当前结点i的权值<结点x的权值,则迭代,即y=x;x=i; 否则: 若此时y=x(即y还未赋值),则y=i; 若此时当前结点i的权值 具体代码实现如下: voidhuffman: : createhuffman() { intn=(hn+1)/2;//n表示不同字符的个数 for(inti=0;i { htree[i].lchild=-1; htree[i].rchild=-1; htree[i].parent=-1; } intx,y; for(i=n;i<2*n-1;i++)//n个需要编码的结点,需要n-1个结点进行哈夫曼构造 { selectmin(x,y,i);//找出权重最小的两个字符 htree[x].parent=i; htree[y].parent=i; htree[i].weight=htree[x].weight+htree[y].weight; htree[i].lchild=x; htree[i].rchild=y; htree[i].parent=-1; } } //寻找权重最小的两个字符 voidhuffman: : selectmin(int&x,int&y,inti) { x=0; while(htree[x].parent! =-1) { x++; } for(intj=1;j { if(htree[j].parent! =-1) { continue;//结束单次循环,直到找到下一个parent=1的htree数组 } x=(htree[x].weight<=htree[j].weight)? x: j; } htree[x].parent=-2;//避免y再次选择x选择的数 y=0; while(htree[y].parent! =-1) { y++; } for(j=1;j { if(htree[j].parent! =-1) { continue; } y=(htree[y].weight<=htree[j].weight)? y: j; } } 时间复杂度: 假设有n个叶子节点,在原有叶子节点的基础上还要新创建n-1个结点才能构成哈夫曼树,每次创建新节点时还要在前n个节点中找到最小的两个结点做为左子树和右子树,则时间复杂度为o(n^2) 3打印Huffman树 voidhuffman: : print(inti,intm) { if(htree[i].lchild==-1)//是叶结点 cout< else { cout< print(htree[i].lchild,m+1);//打印左孩子 print(htree[i].rchild,m+1);//打印右孩子 } } 4创建编码表 主要思想: 建立字符编码表。 这里采用从叶子节点到根节点的顺序逆序编码,然后字符串转置得到最终编码。 如果有n个叶子节点需要n次循环编码 [1]从叶子节点开始循环,直到根节点 [1.1]若该节点师父节点的左分支,则编码0 [1.2]若该节点师父节点的右分支,则编码1 [1.3]将该节点的父节点当做叶子节点进行下一次分析 [2]将编码字符逆置 voidhuffman: : createcodetable() { intn=(hn+1)/2; hcodetable=newhcode[n];//存储不同字符代表的编码 for(inti=0;i { hcodetable[i].data=htree[i].daibiao;//存储字符 intchild=i;//孩子结点编号 intparent=htree[i].parent; intk=0; while(parent! =-1)//自下而上到达根节点之后结束循环 { if(child==htree[parent].lchild)//左孩子编码'0' hcodetable[i].code[k]='0'; else//右孩子编码'1' hcodetable[i].code[k]='1'; k++; child=parent;//迭代 parent=htree[child].parent; } hcodetable[i].code[k]='\0';//字符编码串最后添加结束符 reverse(hcodetable[i].code);//颠倒字符串 } } voidhuffman: : reverse(charm[])//颠倒字符串 { intn=0; chartemp;//设置中间变量 while(m[n+1]! ='\0') n++; for(inti=0;i<=n/2;i++)//将头尾两个字符互换 { temp=m[i]; m[i]=m[n-i]; m[n-i]=temp; } } 时间复杂度: 假设有n个叶子节点,则哈夫曼树深度至少为o(log2n),每次编码都要向上循环到根节点,则编码总的时间复杂度为o(n*log2n) 5编码 进行编码只要能够显示出来编码后的字符串,若A的编码为0,B的编码为10,则字符串AAB的编码显示为”0010”即可。 由于初始化函数中已经记录了输入的字符串s,因此直接使用该变量作为输入即可。 编码前的大小即用户输入的字符串s的大小,编码后数据变成由01组成的字符串,在机器中以二进制数的形式存储,所以需要把原字符串的大小乘以8再和编码后的数据相比较,确定压缩比 voidhuffman: : encode(char*s,string*d)//编码,s为输入字符,d为编码字符 { floatsum=0;//统计字节数 intn=0; while(*s! ='\0') { for(inti=0;i<(hn+1)/2;i++)//找哈夫曼表对应的编码 { if(*s==htree[i].daibiao) { for(intj=0;hcodetable[i].code[j]! ='\0';j++) { *d+=hcodetable[i].code[j]; sum+=1; } s++; n++; break;//跳出本次循环,进行下一个字符的对应编码 } } } cout<<"编码前长度: "<<8*n< cout<<"编码后的长度: "< cout<<"压缩比为: "<<8*n/sum< } 时间复杂度: 假设有原始数据N个字符,出现了n种字符。 一个一个的进行匹配,可知此过程的时间复杂度为o(N*n) 分析压缩比的时间复杂度: 没有循环或递归,所以为o (1) 6: 解码 基本思想: 1从左到右读取编码串 2)从根结点开始,如果是0,则选择左支;如果是1,则选择右支;直到叶结点。 voidhuffman: : decode(char*s,char*d)//s为编码后的字符串,d为解码后的字符串 { while(*s! ='\0') { intparent=hn-1;//根结点在htree中的下标,是有hn个结点,但数组从0开始 while(htree[parent].lchild! =-1)//如果不是叶结点,从根结点开始到终端结点结束循环 { if(*s=='0') parent=htree[parent].lchild;//迭代 else parent=htree[parent].rchild;//迭代 s++; } *d=hcodetable[parent].data; d++; } } 本实验中的主要数据结构htree和hcodetable都是动态内存,因此必须要在Huffman树的析构函数中进行内存清理,代码如下: huffman: : ~huffman(){delete[]htree;delete[]hcodetable;} 时间复杂度: 设编码后的数据d长度为n,解码从头进行到尾,此时间复杂度为o(n) 关键算法7: 直观打印哈夫曼树 思想和普通二叉树的中序遍历相同,需要不断的递归。 要打印一个节点的权值,首先要打印它的右子树,最后打印它的右子树。 同时因为要打出树的形状,函数还需要layer参数存储这是第几层,根据layer的不同决定前面空多少格。 [1]如果到达叶子节点 [1.1]打印空格 [1.2]打印该节点的权值 [1.3]回溯 [2]否则 [2.1]进入该节点的右子树 [2.2]打印空格 [2.3]打印该节点的权值 [2.4]进入该节点的左子树 C++实现: voidhuffman: : print(inti,intm) { if(htree[i].lchild==-1)//是叶结点 cout< else { cout< print(htree[i].lchild,m+1);//打印左孩子 print(htree[i].rchild,m+1);//打印右孩子 } } 时间复杂度: 与二叉树的中序遍历时间复杂度类似,假设有n个叶子节点,时间复杂度为o(n) 3.程序运行结果 1、测试主函数流程: 流程图如图2所示 程序运行图 4.总结 1、调试时出现的问题: 初始化时耗时很长时间,不知该如何统计字符频度,且最后打印时出现问题,思考许久. 2、解决的方法: 认真看课本,查阅相关资料,发现用冒泡排序可以统计输入字符的总数,用for循环可以统计单个字符出现的个数。 打印应用递归函数,同时和同学交流也可加快思考进度。 3、心得体会: 哈夫曼编码根据自负出现的概率来构造带权平均长度最短的编码。 它是一种变长的编码。 它的基本原理是频繁使用的数据用较短的数据代替,较少使用的数据用较长的数据代替,每个数据的代码各不相同,但最终编码的平均长度是最小的。 通过这次的实验,可以初步掌握和理解这种巧妙的结构。 通过深入学习可以理解代码深刻,否则只是简单看并不能解决自己并不清楚的问题,因此必须不断实践,不断练习才能提高水平。 4、下一步的改进: 在自己闲时应看看课本,进行小的练习,这样才能提高水平。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 北邮哈夫曼 数据结构 实验 报告