数据结构课程设计报告示例文档Word文档格式.docx
- 文档编号:18634848
- 上传时间:2022-12-30
- 格式:DOCX
- 页数:38
- 大小:37.85KB
数据结构课程设计报告示例文档Word文档格式.docx
《数据结构课程设计报告示例文档Word文档格式.docx》由会员分享,可在线阅读,更多相关《数据结构课程设计报告示例文档Word文档格式.docx(38页珍藏版)》请在冰豆网上搜索。
5)压缩过程中显示压缩进度并有相关信息提示
6)WinHfm.exe可图形显示源文件的哈夫曼编码构造树
2 概要设计
2.1相关函数介绍
2.1.1dos版本程序下的有关函数
1)voidHaffman(intnodeCode,intlength,intsum,vector<
pair<
int,int>
>
&
hfmCode,vector<
int>
lchild,vector<
rchild)//哈夫曼树编码递归函数
3)voidindexSearch(intnodeCode,vector<
rchild,vector<
index,vector<
&
code)//索引编码递归函数
3)voidmakeIndex(intnodeCode,int&
tt,vector<
index,int&
indexNum,list<
code,vector<
rchild)//索引解码递归函数
4)voidCompress()//压缩函数
5)voidUnCompress() //解压缩函数
2.1.2windows版本程序下的新增函数
1)AnsiStringShowNowTime()//计算当前时间(精确到毫秒,以字符串返回)。
2)voidsearch(intnodeCode,int&
i,vector<
rchild)//递归计算每个节点的X坐标
3)voidsearchdraw(intnodeCode,intheight,vector<
rchild)//递归做图函数
4)void__fastcallTForm1:
:
Paga1OnDrawTab(TCustomTabControl*Control,intTabIndex,constTRect&
Rect,boolActive)//PageControl的标签页的色彩描绘
5)void__fastcallTForm1:
CompareFiles(TObject*Sender)//文件比对函数
当然还有一些相关按钮的响应函数,在此不在赘述。
2.2函数调用说明
图1函数调用关系图
3详细设计
3.1压缩算法
A、核心算法:
文件由若干个字节组成,一个字节有8bits,故有28=256种字节构成形式,对应字节码为0-255。
按此256种字节出现频率可构造haffman树进行重新编码,编码后每字节的新编码平均长度将<
=8bits,将每个字节对应了压缩编码写到新文件中,从而达到压缩文件的目的。
B、哈夫曼树构造算法:
用数组intfre[0..255]表示第0号至第255号字节节点的出现频率,找出最小的fre[a]与fre[b],则构建第256号节点,其左孩子为a号节点,右孩子为b号节点,不妨用数组记录:
left[256]=a,right[256]=b。
fre[256]=fre[a]+fre[b].删除a,b节点。
然后,再在剩下的节点中找出最小的fre[a]与fre[b],构建第257号节点,再删除a,b节点。
由此,每做一次,新生成一个节点,删除两个节点,即减少一个节点。
因而在做255次后,最后的第510号节点即为haffman树的根节点。
又由left[]与right[]数组,该haffman树得到确定。
C、关于B部分的优化
1)每次都得找最小的频率节点,所以存放节点的容器最好是已经排过序的,这样取最小的节点就非常方便了,但新生成的节点就得按顺序插入到该容器中,如果用顺序表则需要查找插入位置,比较费时——本程序采用满足以上条件的最适合容器类:
STL(标准模板库)下的Set集合类,它以二叉排序树形式存储元素,有效地解决了这个问题。
2)某些节点的频率可能为0,(例如英文文档,字节码即ASCII码,在0-127之间,故fre[128..255]一般均为0),此时,就没有必要将这些频率为0的节点加入到哈夫曼数中,因为它们在文件中没有出现,无须重新编码。
D、哈夫曼编码结构及算法
哈夫曼树构造完毕之后,可以利用递归算法遍历该树,给每个叶子编码,如下图:
A编码为0,B为编码100,C为101,D为11。
直观上这样的编码可以用字符串表示,但这样给写入编码到压缩文件造成了困难,因为这涉及到字符串的拆分问题,时间上不允许。
图2一棵哈夫曼树
进而,可以用一个整形数组来表示一个编码例如code[B][0]=1,code[B][1]=0,code[B][2]=1即可表示B节点的编码,这样取某位压缩代码比较方便,但是因而所有叶子的编码实际上是一个二维数组,空间消耗比较大。
在此,现提出新的编码存储结构,一个编码用两个整形数表示,第一个数来表示编码长度,例如code[B].Length=3,第二个数表示编码的十进制值,因为101[2]=5[10]所以code[B].Dec=5。
这样极大地节省了空间。
但似乎这样会给向压缩文件写入编码带来麻烦,难道还要把Dec值再转换为101才能写到文件中?
事实上不需要,而且在速度上反而比前面的结构更快。
关于使用此种编码结构在速度上的优势,将在后面详细解释。
E、压缩编码写入算法——一级32位缓冲器算法
Cpu与i/o设备通讯是并行处理的办法,最小处理单元是一个字节,即8bist,所以希望以bit为单位将编码写到压缩文件中这在硬件上就是不可能的,C++语言也更不可能提供有关的语句了。
因而我们要设置一个缓冲区,当缓冲区中编码达到或超过8bits时,将前8bits做为一个byte写出到压缩文件中。
如果编码存储结构是字符串,那么缓冲区也自然是一个字符串,写入文件时涉及到字符串与数字的转换,效率上是很不划算的。
如果编码存储结构是整型数组,那么缓冲区实际上就是一个数值t,t初始值为0,此时读入压缩编码的第一bit加到t中,然后t左移一位;
再读一bit压缩编码,加到t中,t左移一位;
8次之后,t已经达到8位,将t(此时t是一个〈=255整数,正好是一个字节〉写出到压缩文件中即可。
这是绝大多数人的方案。
但是本软件的压缩编码是用两个整型数字表示的,无法取得某个bit的值,应该怎么办呢?
在VC,C++builder和=dev-c++这些著名的编译器中,int型是32bits,也就是定义inta后,a本身就成了一个绝佳的32位缓冲器[在本设计中,称之为一级缓冲器]。
如前所说,缓冲器8位就够了,a作为32位缓冲器,前面8位可用来缓冲输出,而后面的24位又为一次性向缓冲器输入压缩代码提供了空间,因为哈夫曼树的深度一般不会超过25层(参见附录1),这样32位缓冲器既给压缩代码的输出提供了方便又给其输入提供了方便。
下面就一个具体事例来比较说明。
例如A的编码为10000000111,B的编码为111111*********
按整型数组的存储和8位缓冲方案编码,则先读A的每位代码,达到8位时输出byte=10000000;
再按位读入3位A的剩余编码111,然后按位读入5位B的编码11111,输出byte=11111111,以此类推,既而输出0111111,而B的最后2位与下面的字节编码再合成输出。
而用双整数存储和32位缓冲器方案编码,则可一次性将A的编码(code[A].Dec)读入到缓冲器中(即缓冲器a+=code[A].Dec),此时缓冲器容量为11位,11>
8,输出前8位(用&
操作可屏蔽掉后24位),此时缓冲器清除前8位(a=a<
<
8),然后一次性读入B的代码置入a中,此时a容量为3+15=18位,此时输出前8位,现在a=10位仍然大于8,在输出8位,剩余2位与下面的编码继续组合输出。
显然,无论在运算时间上和存储空间上,后者均占明显优势。
当然后者出现了&
与<
运算,而这样的运算相当于汇编语言的AND与SAL指令,是非常高效迅速的,比从内存读编码快捷,且操作次数也少的多。
F、写入算法另一角度的优化——使用二级1024K缓冲器
在写入编码的过程中,宏观的过程是:
以字节形式读取原文件,然后找到该字节的编码,进而以字节形式写到压缩文件中去。
不停的字节读写会给cpu带来频繁的中断并且硬盘的磁头来回在磁道扇区中移动,减慢了速度。
而如果以数据块的形式读写则可以有效地利用到DMA通讯,减少了cpu中断,并使硬盘磁头连续移动,显著加快了速度。
而c++语言的iofstream类的read()与write()成员函数为此思想的实现提供了可能。
所以,可以开辟一个1024K的缓冲区,先将文件前1024K的字节读入内存缓冲区中(在本设计中,这称为二级缓冲器),编码后的字节也不急于写入文件中,而是先写到另一个二级缓冲区中,当其足够1024K时再以数据块的形式写到压缩文件中。
然后清空缓冲区,继续做下一个1024K的编码写入。
而缓冲区本身,其实就是二个整形数组,read_buffer[1048576]和write_buffer[1048576]。
不过这样的数组声明已经太大了,可以用STL的vector向量类来代替这个数组(vector结构实际也就是一个封装了的加强型数组)。
一级缓冲器在微观上解决了写编码速度的问题,而二级缓冲器则在宏观上改善了写编码的问题,两者关系的嵌套的关系,实际的程序主结构也就是一个二重循环,外层控制二级缓冲器,内层控制一级缓冲器。
G、一些细节问题
采用以单字节为单位构造哈夫曼树,这样数的规模不会太过庞大,构造的时间比较快,并且有比较良好的压缩率(详细的压缩报告见附录二)。
如果以双字节构造,则可能出现叶子数为65536的哈夫曼树,虽然压缩率可以得到相对提高,但已经提升不明显,而整个的压缩过程将变的缓慢。
如果进行智能识别频率采样,一方面采样过程又要花费一定的时间,另一方面,哈夫曼树本身的结构也决定了这样做并不划算,也给解压缩和写入索引带来了许多麻烦。
用left[]和right[]数组来描述一颗二叉树而没有用指针,是为了避免指针可能带来的由叶子到根的反向建树的麻烦;
另一方面,树的节点和叶子数目基本确定,没太多必要使用灵活的指针和相关的内存分配语句。
编码写出后,内层缓冲器可能剩几个bit,没有达到8bit,此时应补‘0’或‘1’以凑齐8位再写出。
文件的大小也不大可能正好被1024K整除,要考虑到最后的剩余部分字节的编码,即要计算好最后的实际字节读取个数,fstream的成员函数gcount()能解决这个问题。
如果把整个压缩过程作为一个函数的话,二级缓冲区的定义最好在函数外作为全局量定义,否则可能栈溢出。
3.2解压缩算法
A、基于字符匹配的解压算法
现在从已压缩过的文件中读取一段代码,如”1001011101……”,哈夫曼树结构入图,先读如第一个字节”10010111”,先取出“1”,在ABCD中均无这个编码;
于是再取出“0”和刚才的“1”组成“01”,仍无此编码;
再取出“0”,组成“010”,发现B叶子的编码与之相等,此时解码得B,输出到解码文件中,以此类推。
这是最容易想到的方法,但效率很低。
首先,取出一个编码后要和所有叶子的编码比对;
其次,编码比对是基于字符串的比对,比较慢。
对于前者的改进可以通过:
1.一旦比对成功就不再和剩下的比对2.按照编码的长度之后长度相同的编码比对等等。
而后者的改进则出现了B算法。
B、基于数值匹配的算法
既然字符比对比较慢,我们可以把编码用2个整数表示,前者是编码的十进制值,后者是编码长度。
这样只和编码长度相等的十进制相比就可以了。
这样把字符串的比较优化为数字比较,据测算,速度提高了约10倍。
但是这两种算法都基于与叶子编码比较,相当于不断的尝试,解压速度很难突破100KB/s。
C、基于循径法
既然所有的编码都隐藏在一个树中,那么根据遇0向左走遇1向右走的方法自然就能很容易地找到叶子,从而得到解码。
仍如前例,读入”1”向右走,发现不是叶子,继续;
读入”0”向左走,发现不是叶子,继续;
读入”0”向左走,发现是叶子B,即可解码为B写入解码文件。
也就是说,循径法充分地利用了每次读进来的编码,能够以确定的路径找到叶子而不需要尝试。
不过要使用这种方法,就必须想建立一个原文件的哈夫曼树,详见索引算法部分。
使用此方法后速度有极大飞跃。
D、缓冲输入输出
和压缩时采用1M二级缓冲相同,如果的压缩时也采用此技术,也会有很大的速度优化,当然程序也变得更加复杂。
E、细节问题
读入和写出仍然基于字节单位,基于位的操作同样要在程序内部通过位运算完成。
最后一个字节在解码时必须注意到压缩时最后一个字节是用”0”或”1”填充的,要避免多余解码,可以在索引中写入文件大小,详见下节。
3.3文件索引算法
由解压缩的算法可知,一个压缩了的文件解压缩的前提是知道原文件的具体每个字节的编码。
这些信息称为索引。
上页的细节问题中提到最好在压缩后的文件中标出原文件的长度,这也是索引信息。
一般索引信息放在文件的最前部分。
最直接的方法就是,把每个字节的编码写到压缩后的文件中去。
如图2,先写入’A’及其编码0,接着是‘B’及编码100,’C’101,’D’11。
即:
01000001001000010100010000111010100010011
‘A’‘B’‘C’‘D’
当然直接这样写会给阅读信息造成困难,因为计算机不知道’A’的编码是几位,它读入0后就不知道是否下一位究竟是‘A’的编码还是’B’的ASCII的开始。
所以我们还得标识出编码的长度A1B3C3D2,这样的确是区别开了,没有歧义,可是编码变长了,我们可以规定叶子是按顺序排列的,此时编码就变短了,即:
00000000000000011100000000111010000001011
A的长度B的长度C的长度D的长度
事实上最大一共有256个叶子,计算机依次读256次就可以了。
这样索引占用的长度一般是512K左右。
不过一旦一个文件只有5片叶子,也得有256个字节来标识编码长度,这在压缩只有几个字节的小文件时,反而文件会扩大几十倍。
如果我们知道原文件的哈夫曼树的结构,也自然就可获知每个叶子的编码,那么,把这棵树的结构作为索引存入文件也可以,如果树可大可小,索引的长度也会可大可小,解决了B方法索引长度始终大于256字节的问题。
如上图,如果非叶子节点为’#’,这个树的结构编码就是”#A..##B..C..D..”
而且哈夫曼树有一个性质,如果一个节点有左孩子,就一定有右孩子,如果没有左孩子,也必然没有右孩子。
由此没有必要再用点号来表示叶子了,因而树结构简化成”#A##B#CD”,再用1表示叶子,0表示非叶子,则为”01001011”,这就是它的存储实现。
如下:
000001000100000101000010010000110100010001001011
有4个节点‘A’‘B’‘C’‘D’树结构
对于一棵完整的有256个叶子的haffman树,大约需要320字节就可以存储它了。
比前面的方法节省了38%。
当然,要使用这种方法,就必须在压缩时用一个递归函数来遍历这棵树以获得树结构编码。
D、关于索引的解码
AB两种建索引的方法都很方便于索引的解码,但空间占用大后者灵活性差,而若使用C方法,则索引的解码也成了问题。
换句话说,我们得由“01001011”来还原出一棵haffman树。
本系统是这样做的,首先得把树结构编码从文件中读到一个数组中,把叶子编号读到另一个数组中,然后由这两个数组用递归的方法造出树。
然后由这棵树再求出每个叶子的编码。
当然这一步可以略去了,因为解压缩采用寻径法,不需要再求每个叶子的具体编码了。
E、相关细节问题
为了给正文部分解码代码方便并显示解码进度,本系统在压缩的文件开头的四个字节记录了原文件的长度。
索引中,节点的顺序和结构树的顺序必须相同,例如都采用先序,中序或后续,本系统采用先序。
索引的编码和解码都用到了递归,而递归的参数都相当多而且很多是数组,为了节省空间,要运用引用操作。
3.4哈夫曼树显示算法
A、简介
在本系统的windows版本的程序中,有显示哈夫曼树的功能,这涉及到了计算机做图以及一些具体的算法。
B、节点的布局
一棵树在描绘之前必须要计算好每个节点的坐标,否则漫无目的地从头节点分左右画下去就很可能造成节点位置的重合。
Y轴的坐标比较好控制,因为它有节点的深度决定了。
X轴呢?
本系统利用中序遍历haffman树,对叶子节点X坐标递增的方法来确定的。
例如图2树的中序依次遍历了ABCD,于是ABCD的横坐标就是1,2,3,4。
而非叶子节点的横坐标取左右孩子的横坐标的平均值,显然这是一个递归算法。
C、具体的描绘
知道每个节点的坐标后,就可以开始描绘了,画圆与直线的函数都有了,因而遍历所有的节点也就可以把整个树给画出来了。
D、细节问题
计算坐标和描绘节点都是递归方法,因为程序的主体就是二个递归程序。
由于节点众多,整个树画出来需要非常宽的幅面,大约5000个象素的宽度。
在windows98系统中不支持如此大的幅面,而在window2000和windowsXP中支持,因而本系统作图功能不能在win98下体现甚至出现异常而终止了整个压缩程序。
因而作图这一部分得使用try/catch这样的异常处理机制以确保压缩程序在各个系统的稳定运行。
画出来的图比较大,一个屏幕显示不下,而仅使用滚动条又比较麻烦,因而本系统采用了“手抓”功能,可以用鼠标拖动画面。
3.5文件比对算法
本系统具有文件比对功能,它的算法如下:
首先,如果两个文件长度不相等,显然文件不相等,无须进一步比较了。
怎么知道指定的文件的长度呢?
如果把文件读一遍当然可以知道它的长度,但这样太消耗时间。
可以利用<
io.h>
库的filelength函数来直接求得文件长度。
如果两个文件长度相同,则可以正式比对。
同样为了加快速度,在此也用了全部变量的缓冲器。
文件A可以用1M的读入缓冲器,文件B可以用1M的写出缓冲器。
然后一一对比,一旦出现不相同,则停止比对,输出“不相等”,程序返回。
如果均相同,则文件相等。
至此,整个算法的描述都已经叙述完了,本系统采用的算法均为以上各部分的最优算法,因而程序的结构比较复杂。
4调试分析
表1调试分析过程
序号
时间
相关信息
1
10月30日
开发dos版,基于字符串编码
2
11月6日
编码存储结构改为双整数表达,改进了解压缩的性能。
3
11月7日
采用长度与数据分开存储的方法构建索引减少了索引长度
4
11月16日
基于位操作编码及解码,大幅度提高效率
5
11月18日
编码方案采用二重缓冲,进一步加快效率
6
11月20日
采用寻径法解码,提高解码速度,并采用树索引,取代代码索引
7
11月22日
开发windows版本
8
11月26日
加入编码部分的进度提示
9
11月29日
加入选取文件功能,交互性更强
10
12月4日
图形显示哈夫曼构造树
11
12月5日
改进显示外观使树结构更加对称
12
12月6日
加入“手抓”功能
13
12月10日
更正了某些文件名不能压缩的问题
14
12月12日
加入文件比对功能
15
12月13日
更正了压缩时可能出现的一个重大错误
16
12月14日
完善用户操作部分,避免了某些误操作
17
12月15日
解决了win98下因无法作大图而终止运行的问题
5用户使用说明(略)
6测试结果
6.1各种类型文件的压缩率测试
表2压缩率测试结果
文件大小
压缩后
压缩率%
文本文件(txt)
文件1(英文文本1)
77465
51566
66.57
文件2(英文文本2)
65194
35116
53.99
文件3(中文文本1)
289772
221695
76.52
文件4(中文文本2)
110421
66088
59.85
文件5(混合文本)
167979
132181
78.69
Word文档
(doc)
261879
134172
51.23
146432
107809
73.62
50688
30836
60.83
28672
15090
52.62
837632
649675
77.60
网页文件(htm,mht)
文件1(无图片)
4317
3076
71.25
文件2(有图片)
504539
373441
74.01
文件3(有图片)
165832
110556
66.74
幻灯片文件
(ppt)
文件1
199680
141151
70.68
文件2
102912
56669
55.06
电子表格文件(xls)
33280
15452
46.43
358400
195286
54.48
位图文件(bmp)
文件1(16色文件)
308278
283395
91.92
文件2(256色文件)
137218
40755
29.70
文件3(24位真彩)
430214
263175
61.17
可执行文件(exe)
94208
67453
71.60
663552
497400
74.96
文件3
1566278
1459349
93.17
6.2速度测试
为避免偶然因素,本项测试选取一个600M左右(621889873byte)的虚拟光驱文件,压缩三次,取平均值。
表3速度测试结果
压缩时间
压缩速率
解压缩时间
解压缩速率
第一次
110.297s
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 课程设计 报告 示例 文档