ImageVerifierCode 换一换
格式:DOCX , 页数:20 ,大小:269.76KB ,
资源ID:4132468      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/4132468.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(哈弗曼编码C实现二叉树的图形化输出已经界面.docx)为本站会员(b****4)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

哈弗曼编码C实现二叉树的图形化输出已经界面.docx

1、哈弗曼编码C实现二叉树的图形化输出已经界面软件工程学院程序设计实践(下)实验报告题目:哈夫曼编码/译码器姓名:张 祺学号:10109345班级:软件工程3班一、 问题解析(对问题的分析、解题思路与解题方法)问题主要分为三部分,分别是文件的操作、界面的设计、文件的压缩,其中界面的设计和文件的压缩又是基于哈弗曼编码的,因此,构造哈弗曼树和获取哈弗曼编码是程序的关键。1、文件的操作: 程序中文件的操作主要是文件的读和写,关于这点,基本上没有遇到什么问题。2、界面设计前面已经提到,界面设计是基于哈弗曼树和哈弗曼树的小时,我分到了这一块,下面做介绍。由于哈弗曼树是正则二叉树,即对于每个结点,要么其左右孩

2、子都存在,要么都不存在。但对于树形结构来说,其变化多端,每一行的结点数不确定,虽然第二层(第一层只有根结点)和最后一层结点数没有任何确定的关系,但他们之间却有相互制约关系,即底层结点的个数关系着上层(尤其是第二、三层)结点之间的距离,这也是最难把握的地方。最初,我用矩阵保存同一层结点之间的关系,但后来发现,兄弟结点的关系容易获得,但如果两结点不是兄弟结点而是堂兄结点时,这种关系就比较难找了,再不理想一点,如果两结点在的根结点父节点来自不同的结点,这种关系就更难确定了。然后,我又想想到了从根结点出发仿照先序遍历的原理逐个打印结点的信息。很快,我发现,这种方法也是不行的,原因在于在先序遍历时可也借

3、助栈或者递归栈来储存后面要访问的元素。但是想要找到这个元素的具体位置比较难,所以这种方法也很快被否认。一直找不到可行的办法,想过用打表,也想过放弃。但是实在不想,一个原因是这是我的任务,如果我放弃或者做的不够完美,这将会影响我们组所有同学的成绩。另一个原因就是我一直认为,只要是能想的到的,就能用代码实现,这也是编程的精髓。无奈之下,我开始查找资料,突然,一张满二叉树的图片(其实是一张家谱图)给了我灵感。我为什么要根据结点选位置呢?完全可也根据位置确定这点的结点啊所以我的最初构想是先打印出一个树形结构,然后根据每个结点的信息,将其输出到指定的位置,最后我就是用这种方法做到的。3、构造哈弗曼树、获

4、取哈弗曼编码 构造哈弗曼树的关键在于每次将权值最小的两个结点合并成一个新结点,如此循环,直到只剩一个结点。在获取编码时,从每一个叶子结点出发,访问哈弗曼树的根结点,所走过的路径刚好和编码相反。二、 任务分工及进度计划我们组的成员还有王倩、求春磊、章力挺。最初分工时,王倩负责文件的读写操作;求春磊负责文件的编码压缩这一块;章力挺负责构造哈弗曼树、获取哈弗曼编码这一块;我负责疏导图形化输出、界面这一块。总体来说,小组的进度比较慢,原因在于第一个程序还没写好。而且我的任务在中间,只有有了哈弗曼编码才能进行树形结构的输 出,所以在开始时,前面的问题我也解决了。下面谈谈我的见解。文件的读写基本上没有什么

5、大的问题,在统计字符频率时,我统计了128个常见字符,用了点小算法,效率比较高。在构造哈弗曼树时出现了瓶颈。究其原因,主要是以前没有接触过哈弗曼树,只有老师在上课时提到过,所以,面对一个新问题,首先需要时间去熟悉。等到哈弗曼树构造完成 ,获取编码也就相应比较简单了三、 数据结构选择、算法设计前面已经提到,这个程序我基本上全都做了,现在就出现的问题 分析。1、 统计字符频率 常见的字符有128个,如果检索一个一个判断,那样的代价会很大的。由于128个字符分别对应1128的ASCII码值,所以可以定义一个128单元的数组,用字符代替下标。这样一来,对于每一个字符,其计算频率其出现一次,下标为该字符

6、的元素递增1。这样可以高效的简单的统计每一个字符出现的频率。2、 构造哈弗曼树、获取哈弗曼编码在问题分析中叶提到,构造哈弗曼树的关键是每次将两个权值最小的结点合并成一个新的结点。教材上采用0号元素不用,通过判断结点的父结点是否为0确定该结点是否要被计算。在我的计算中,我用0号结点,而且另外开辟了一个存放结点权值的副本数组,这样一来,每次都是对结点判断,被合并的结点权值变为-1,最后用副本还原所有结点的权值。结点的结构体定义如下:struct Hafuman int weight; /权值3 int parent; /父结点的位置 int left_child; /左孩子的位置 int righ

7、t_child; /右孩子的位置;在构造哈弗曼树时,所有结点(包括本来有权值的结点和通过合并的到新的权值的结点),其地位是等价的,因此,最好把两种结点放在一起,这样方便访问。所以在有128种字符的前提下,开辟128*2个结点,前128个结点存放本来就有权值的结点,后127个结点存放新合成的结点,这样一来,访问两种结点就只需要一个循环。由于128个结点不是很大,所以用线性结构的数组存储。在获取哈弗曼编码时,由于是从前128个结点中有权值的结点开始访问根的,因此访问得到的编码正好和要得到的哈弗曼编码相反,估只需逆转就可得到哈弗曼编码。3、 图形化输出树形结构前面提到,在输出树形图时采用有点类似模板

8、的方法输出树,即先打印全部树枝,再根据情况条件树叶,最后擦掉多余的树叶。但是,这种方法任然有缺陷。根据二叉树的性质,第k层的结点个数最多为2k-1,当是第7层时,需要结点26即64个结点。尽管对界面的宽度做了扩展,但在一列打印64个结点仍然很难做到,或者即使做到,效果也不一定理想。所以最后树的深度在6以内,即对于深度大于6的树,只输出前6层。在打印树形结构时,将每一层第一个结点所在的坐标记录下来,在打印结点时首先找到这个坐标,这样一来,每层头结点坐标相当于提供了一个借口。由于已经打印出了满二叉树的树枝,这样一来当这个位置没有结点时,最好把它坐在的树枝擦掉,以免影响美观。擦除和打印的类似,不同的

9、是擦除只是打印空格。为了界面美观,我将树枝的颜色和结点的分成了两种,定义了color( int n )函数这个函数的功能在于调用这个函数的时候光标打印出的字符全部变为指定的颜色。4、编程与程序清单 前面已经提到,这个程序的大部分问题我自己都解决了,现做一下说明。 1、统计字符频率 在定义好结点的结构后首先声明一个2*N大小的数组,为了所有函数调用的方便,把这个数组声明为全局变量。 首先是打开文件if(fp = fopen(C:hafuman.txt, r) = NULL) printf(cannot open the file!n); exit(0); 等打开文件后,通过一次循环就可以统计所有

10、字符的频率 c = fgetc(fp); while(c != EOF) ac+; /a N 是结点权值的副本 nodec.weight+; /结点权值递增 length+; / 统计文件长度的变量递增 c = fgetc(fp); /获取下一个字符就这样,只需要循环一次就可以得到所有结点的权值 在打印统计结果时,我用了表格。由于有些特殊字符,比如:换行(r)、回车(n)、空格( )、tab(t)等键无法打印,或者即使打印出来也看不见,所以,对于特殊字符,我输出了他们的中文名称。由于有128个字符,如果使用同一种颜色打印,未免太单调,而且不易区分,所以,我有分别设定了表格、字符、出现次数的颜色

11、,把它们区分开来。最后的效果如下:就这样,完全打印输出了128个字符出现的频率。在这里特别声明,由于默认的窗口太小,我用了拓展窗口的函数,system(mode con cols=130 lines=38);这即表示窗口是130列,38行。同样方法,在打印哈弗曼编码时也用该方法,效果如下:2、构造哈弗曼树、获取哈弗曼编码 由于前面已经统计了每个字符出现的频率,这些频率就是字符的权值。构造哈弗曼树的关键点是每次将权值最小的两个结点合并成一个结点,而且保证合并过的结点此后不会再被统计。我设计是将没出现的字符频率即权值初始为-1,出现的,其权值就是1、2、3、4.。这样避开了0的特殊性,由于在统计字

12、符时,统计的是最小的,而0也可以算是最小的,这样会带来不便。所以,直接用正、负两种状态区别。具体代码如下:void Greate_Hafu() int min_1, min_2; int Left, Right; int i; for(i = N; i 2 * N; i+) Find_Min(min_1, min_2, Left, Right); /* 找到两个权值最小的结点,最小权值为min_1、 min_2,最小 权值所在位置为Left、 Right*/ if(Right != -1) / 有最小两结点 nodei.weight = min_1 + min_2; / 新结点 ai = no

13、dei.weight; / a N 与node.weight N 同步 aLeft = nodeLeft.weight; aRight = nodeRight.weight; nodei.left_child = Left; nodei.right_child = Right; /* 新增了结点,该结点是刚最小权值两结点的父结点*/ nodeRight.parent = i; nodeLeft.parent = i; /当前最小两结点的根结点为刚产生的新结点 nodeRight.weight = nodeLeft.weight = -1; / 当前权值最小的两结点权值清0,以便后面的选择 fo

14、r(i = 0; i 2 * N; i+) nodei.weight = ai; /最后对所以结点的权值还原由于前N个结点存放都是出现字符的权值,所以在合并新结点时,新结点的位置一定在N2*N直接。所以在访问1N之间的元素时,循环用的是 for(i = N; i 2 * N; i+),这样当找到两个权值最小的结点时,将其权值赋值给一个新的结点,而这个新结点一定在N2*N之间。找到的权值最小的两个结点其父结点都指向了新的结点,为新结点的左、右孩子也分别指向了两个权值最小的结点。这一年以来,相当于建立了一个双向二叉树,即通过父结点可以直接找到其左、右孩子结点,也可以通过左、右孩子结点直接找到其父结

15、点,这么做的原因主要是为了后面获取哈弗曼 编码的方便。由于已经建立好了哈弗曼树,那么,获取哈弗曼编码的过程就是从叶子结点出发,访问根结点的过程。具体代码如下:void Get_Code() char str2*N; / 声明一个临时存储编码的数组 for(int i = 0; i 0) / 权值为正时才可能有父结点/只有权值不为-1的结点才有其父结点 int p = i, k = 0, j = 0; memset(str, 0, sizeof(str); /临时数组全部置0/*找到了一个有权值的结点时,需要从此结点出发,访问根结点*/ while(nodep.weight 0) int par

16、ent = nodep.parent; if(nodenodep.parent.left_child = p) /判断是当前结点是左孩子 strk+ = 0; /左为0 else if(nodeparent.right_child = p) strk+ = 1; /右为1 p = parent; parent = nodeparent.parent; / 父结点继续访问根 /这个循环相当于是递归的过程 while(k-) hafucodeij+ = strk; /最后编码赋值,(是逆序编码) 由于02*N之间的每一个结点都可能存有有权值的结点,所以循环是从02*N。由于前N个结点存放的是本来就

17、有结点的权值,也就是说,前N个结点存放的恰恰是叶子结点,其所在的位置也应该是树的最下层,其父结点一定在N2*N之间。但是,由于N2*N之间的结点可能还有父结点,因此在N2*N之间也要循环。最后到01字符串是从下往上的编码,而真正的 哈弗曼编码又是从上到下的,刚好相反,所以需要逆转。3、图形化输出 前面已经说了,我图形化输出时,先打印了树形结构,在打印各个结点。在打印树枝时,我用了暴力的方法,即一层一层输出,并没有用规律循环。但是,规律肯定是有的,只是我的深度只有6,找规律还不如直接打印来的快。最初打印的树枝图如下所示: 这样一来,在打印结点时只需找到其所在层的入口。 int x = 1, y

18、= END - 10; local50 = x; local51 = y; gotoxy(x, y - 1); for(i = 0; i 16; i+) printf( ); x += 3, y -= 2; local40 = x; local41 = y; gotoxy(x + 1, y - 1); for(i = 0; i 8; i+) printf(t t );其中local 5 5 是声明的每一层入口坐标的起始坐标的全局变量上述代码实现了第6、5层树枝的打印。上面所示图形是左右完全对称的,但是,在打印结点时,由于仅仅知道每层的入口地址,而不知道某个结点的具体位置。因此在打印结点时需慢慢

19、尝试。for(i = 0; i 2 * N; i+) / 第一个 if(nodep.left_child = i) Print_Data(local30, local31, i); for(i_1 = 0; i_1 2 * N; i_1+) if(nodei.left_child = i_1) Print_Data(local40, local41, i_1); break; for(i_1 = 0; i_1 2 * N; i_1+) if(nodei.right_child = i_1) Print_Data(local40 + 8, local41, i_1); break; for(i_

20、1 = 0; i_1 2 * N; i_1+) if(nodenodei.left_child.left_child = i_1) Print_Data(local50, local51, i_1); break; for(i_1 = 0; i_1 2 * N; i_1+) if(nodenodei.left_child.right_child = i_1) Print_Data(local50 + 6, local51, i_1); break; for(i_1 = 0; i_1 2 * N; i_1+) if(nodenodei.right_child.left_child = i_1)

21、Print_Data(local50 + 8, local51, i_1); break; for(i_1 = 0; i_1 0) if(i N) if(i = 10) printf(换); if(i = 32) printf(空); /特殊字符打印中文名称 else printf(%c, i); /如果是出现的字符,为了直观,打印字符 else printf(%d, nodei.weight); / 是新合并的结点时只需打印权值即可 else /通过判断nodenodei.child.weight来擦除 if(y = local51) /第6层的擦除 int k; for(k = 0; k

22、130; k+) if(1 + k * 8 = x) gotoxy(x + 1, y - 1); printf( ); break; for(k = 0; k 130; k+) if(7 + k * 8 = x) gotoxy(x - 1, y - 1); printf( ); break; if(y = local41) / 第5层的擦除 gotoxy(x + 1, y - 1); printf( ); gotoxy(x + 7, y - 1); printf( ); if(y = local31) /第4层的擦除 for(k = 0; k 3; k+) gotoxy(x + k + 1,

23、y - 1); printf( ); x+, y-; y += 3, x -= 3; for(k = 0; k 3; k+) gotoxy(x + 14 - k, y - 1); printf( ); x-, y-; if(y = local21) /第3层的擦除 for(k = 0; k 7; k+) gotoxy(x + k + 1, y - 1); printf( ); x+, y-; x -= 7, y += 7; for(k = 0; k 34) /超出行数 puts(tt文字过长,部分未予显示); break; 5、测试方法、测试数据与测试结果 起始界面如下 其中,对文件操作又包含

24、几个子选项 测试数据主要从文件中读取,加入读取字符串HANGZHOU DIAN ZI KE JI DA XUE字符频率显示如下:树形结果图如下:字符编码如下:编码文件如下:文件对比显示如下:5、总结 通过这个程序,我学会了怎么面对一个陌生问题。首先是构造哈弗曼树,虽然方法教材上有所介绍,但那毕竟是教材,虽然经典、简介,但毕竟不是自己掌握的,我们要做的就是怎样把教材的大小化为自己的知识,教材只是起指导作用。比如,“每次将权值最小的两个结点合并成一个新结点”,这才是教材教给我们的,至于怎么实现,我想每个人都有自己的方法,只是简易程度。 图形化输出树形结构,这个问题从没遇到过,而且也没思路,一味的盲干是不会其任何效果的。只有现确定方法(如我采用的是现打印慢二叉树的树枝,再添加叶子),问题的关键点就是确定这个方法。至于用代码实现,一般来说不会有什么问题。 这个程序是以团队形式完成的,在小组中,组员之间存在水平差异,有时候自己很急,但组员不尽力,这给自己的继续带来了不便。可能是由于这是个小程序,组员之间的协助体现的不是很具体。但在以后的生活中,我们必须要有团队精神,特别是对于我们专业的同学。问题的全部解决归功于不断的思考。还是那句话,我坚信只要是能想到的,就能用代码实现。热门网 http:/www.remen.org 32T4DAzl8z9L

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1