哈夫曼树课设Word格式.docx
《哈夫曼树课设Word格式.docx》由会员分享,可在线阅读,更多相关《哈夫曼树课设Word格式.docx(7页珍藏版)》请在冰豆网上搜索。
2.2开发环境2
3详细设计2
4所遇到的问题和分析解决5
5系统特色及关键技术6
6结论7
引言
在当今信息时代,信息技术已成为当代知识经济的核心技术。
我们时刻都在和数据打交道。
随着计算机技术和通信技术的发展,处理和传输的数据量越来越庞大,如何采用有效的数据压缩技术引起了人们极大的重视。
在现代通信技术中,常用二进制数字0,1传输数字和符号。
当传输字符如a,b,c,d….等字符时,常常用二进制数字传输。
哈夫曼编码是一种应用广泛且非常有效的数据压缩技术,该技术一般可将数据压缩20%~90%,其压缩效率取决于被压缩数据的特征。
利用哈夫曼编码进行通信可以大大提高通信道利用率,缩短信息传输时间,降低传输成本。
所以,就要求在发送端通过一个编码系统对待传数据预先编码。
在接收端将传来的数据进行译码。
此设计性实践设计这样一个简单编/译码系统。
此系统虽然不够强大,但也是完成强大系统的基础。
此次简单设计,意在加深对哈夫曼树的理解,熟练辨别在什么时候用那种数据结构,以及掌握怎么进行需求分析以及对所需要的功能,怎样去实现所定义功能,怎样去优化所做的系统。
1系统概述
在设计系统的整个过程,本人所要做的:
分析系统所要实现的功能,考虑整个算法中的各个模块,及所涉及的数据结构和算法,以及怎样构建一棵比较合理的哈夫曼树。
系统所具有如下功能:
1.接收客户所输入的数据:
接收输入的n个字符,以及n个字符的频率(权值)
2.构建哈夫曼树并编码:
用所接收的字符及权值按左子树根结点的权小于等于右子树根结点的权的次序构造哈夫曼树,并求出每个字符的哈夫曼编码(用二进制数表示)
3输出编码:
根据所建立的哈夫曼树输出编码
4.译码:
将输入的二进制数字字符根据所存储的编码通过哈夫曼树译成字符
5输出哈夫曼树
2需求分析
2.1系统需求
设计目标是要实现Huffman的编码和译码。
编码的工作过程如下:
首先读入待编码的字符及其权值,这就要求有一个能接收所输入的字符的函数;
然后根据所输入的字符构建哈夫曼树,所以需要一个构建哈夫曼树的函数;
根据构建的哈夫曼树对所输入的字符进行编码,需要一个编码函数,另外能将编码后的字符及其码文输出;
对码文的译码,接收输入的码文,通过哈夫曼树进行译码,因此需要一个译码函数。
综上所述,要实现使用哈夫曼树进行编码译码,需要一个能接收输入字符及数字的函数,对哈夫曼树进行初始化的函数,构建哈夫曼树的函数,对字符进行编码的函数,对码文进行译码的函数。
用户界面。
需要提供一定的提示,因为这是一个有相当规模的程序,分的模块很多,完成的功能也不少,为了用户能方便的操作,需要一个合理的用户界面,提示操作(如必须先建树才能进行编码译码,并且,必须先编码才能进行译码)。
并且,对于用户的输入,系统需要一定的输出提示。
在设计过程中,要注意,若用户所输入的数据与系统的设计要求的不符合,应该有相应的错误提示,及提示处理操作等。
2.2开发环境
用于开发哈夫曼树的环境:
VC
开发语言C++
3详细设计
哈夫曼树的编码和译码的实现,其包含的功能模块分为:
1.定义哈夫曼树结点结构、哈夫曼编码结构的,哈夫曼树类,以及各项功能函数的头文件MakeHufm.h。
2.实现哈夫曼树中各函数的MakeHufm.cpp文件。
包括对哈夫曼树进行初始化的HuffmanT();
接收输入的字符及其权值MakeHuffman()函数;
对各叶子结点的排序soft()函数;
构造哈夫曼树CreateHftree()函数;
后续法验证树的postorder()函数;
对输入字符进行编码的Coding()函数;
对码文进行译码的decode()函数。
3.main()函数
1.头文件MakeHufm.h
在此对哈夫曼树的结点结构进行定义,用weight存放字符的权值,data存放字符,用lchild、rchild、parent标记左、右孩子及父亲结点的下标。
根据哈夫曼树的功能定义哈夫曼树类,并声明各功能函数及声明在建树过程中起到辅助作用的私有变量root跟HuffmanTreeNode类型的数组A[]。
2.MakeHufm.cpp文件
初始化的HuffmanT()函数
通过将哈夫曼树的根的下标置为0,对哈夫曼树进行初始化。
接收输入的字符及其权值MakeHuffman()函数
通过一个for循环,将输入的字符及其权值赋值到数组中每个结点相应的data域与weight中。
CreateHftree()函数
此函数参数为输入结点总数。
在此函数中,可以分出两部分。
第一部分是while循环,此循环控制的是由叶结点创建哈夫曼树的过程。
第二部分是while循环中的两个for循环,这两个for循环是用来创建内结点从而创建哈夫曼树的。
在第二部分中,这是用到了哈夫曼结点的左、右孩子,父亲结点的标识域。
第一个for循环,将权值最小的结点找出,给出标记,并将新结点的下标值赋予其父亲结点的下标,同时也将该结点的下标赋予其父亲的左孩子的下标。
用第二个for循环找出次小的结点,也给出标记,并将新结点的下标值赋予其父亲结点的下标,同时也将该结点的下标赋予其父亲的右孩子的下标。
这样就构建了一个非叶结点,并且将它的左右孩子都指定好,它的权值也就是权值最小结点及次小结点的权值的和,也就是左、右孩子的权值的和。
而对于已经已经比较过的结点,通过将其parent赋值及不再等于初始的-1,通过if语句中规定的条件,及对于父亲还没有赋值的结点可以放入比较,来保证比较过的结点不会再被比较,保证结点的建设过程不会产生混乱。
每当一个非叶结点后,将根下标root指向这时结点的下标n+i,这样树就能一步一步建设起来,进行下一次的循环,直到所有叶子结点都拿来比较并建立起内部结点,也就建好了哈夫曼树了,并且它的根下标为root。
返回根,就可以对该哈夫曼树进行遍历,编码的后期操作。
postorder()函数
此函数参数为一个整型变量,最为下标,在主函数中传进的这个参数,也就是树的根,此时就可以对树进行遍历了。
在此,选择了后序遍历的方法,因为对于哈夫曼树,叶子结点也就是包含data的结点。
因此要先读入它的叶子。
而非叶子的结点的data域是空的,并且读出并没有什么意义,通过if语句来保证操作的是叶子结点。
Coding()函数
此函数的参数为一个整型变量,它表示的是输入字符的字数。
该函数能实现由叶子向根求每个字符的编码。
对于输入的字符,原先是存放在HuffmanTreeNode类型的数组中的,而且前面也已经建好了哈夫曼树,从数组下标为1的元素开始读(因为数据是从1开始存放的),读它的父亲结点,若这个元素的权值等于它父亲左孩子的权值,则将定义的HuffmanCode类型数组的第一个元素(这个数组的大小与输入字符的个数对应)的bits[i],标记为0,;
反之,若等于右孩子的权值,则bits[i]标记为1;
继续进行循环,继续向bits[]中加入编码。
总之,这个函数是利用for语句由根结点向上直到根结点编码,然后通过运用HuffmanCode中的数组bits[N]从下标为cnt开始输出编码,反方向输出正好是每个字符的编码。
decode()函数
此函数的参数有三个分别为数组的数组名,表示该数组长度的整型变量和代表已建的哈夫曼树的根结点下标的整型变量。
该函数的功能是实现解码。
首先将一串编码存放到数组B中,然后该函数通过for语句读出存放在该数组B中的‘1’或‘0’,实现哈夫曼树由根结点到叶子的译码,利用存放根结点数组的下标Treeroot找到根结点,然后从根结点出发,如果B[i]等于1则为右子树,等于0则为左子树,一直往下找直到找到叶子结点并输出存放在该叶子结点中的字符,然后再回复到根结点重新译下一个码,直到数组元素都被读完为止。
此部分主要用来完成用户界面的简单设置,链接各项功能。
并对用户输入提示操作等。
在这个函数里首先接收要输入的字符的个数,然后显示选择操作的各选项,输入操作代号,进行相应的操作。
4所遇到的问题和分析解决
在组建电文的编码和译码系统的过程中,主要遇到的问题是:
1.哈夫曼树结点结构的选择2.哈夫曼树存储结构的选择3.构建哈夫曼树的算法的选择4.编码和译码的实现5.用户操作设置,处理,完善
1.哈夫曼树结点结构的选择
由于哈夫曼树(最优二叉树)的思想是:
有n个叶子,将两个权值最小的叶子的权值相加作为他们父亲结点的权值,而这两个叶子作为左、右孩子,再将所得的结点作为内部结点与剩下的叶子结点的权值进行比较,找出两个权值最小的重复以上的操作,直到建成一颗有n个叶子和n-1个内部结点的哈夫曼树(最优二叉树)。
所以该树中的结点都必须具有保存结点权值的数据域和指向自己左、右孩子以及父亲的指针域,当然保存结点字符的数据域data是不能少的,于是设计了HuffmanTreeNode类型的结点来达到这个目的。
2.哈夫曼树存储结构的选择
对于数据的存储,有顺序存储和链式存储。
由于哈夫曼树(最优二叉树)不一定是一颗完全二叉树,所以用保存完全二叉树的方式保存最优二叉树进行保存是不合适,所以算法用顺序存储的方式保存哈夫曼树。
3.构建哈夫曼树的算法的选择
在进行建树的过程中,一直在进行建树过程遇到困难,到底应该怎样合理的构建一课二叉树。
在之前的算法中,被指出了所构建的树的结点在以后结点比较的时候会不方便。
但是,当时没有注意到其实,用到下标也可以很灵活。
因为根据所定义的结点类型,每个结点都有左、右孩子的下标,以及父亲结点的下标。
因此可以通过将结点的左、右孩子下标,及父亲的下标赋以相应的值,就可以将每个结点的关系搭建起来,例如源代码中的
A[flag1].parent=n+i;
A[flag2].parent=n+i.A[n+i].lchid=flag1,A[n+i].rchild=flag2
等,就可以将下标标记为flag1和flag2的两个结点作为左、右孩子联系起来,当然它们有共同的父亲。
这样,一棵树的基础就可以建立起来了,即一个结点,带有左、右孩子。
类似地,可以将一颗哈夫曼树建立起来。
4.编码和译码的实现
在建立好一颗哈夫曼树后,就可以利用哈夫曼树进行编码译码了。
但对于这颗哈夫曼树,该怎样下手去对字符进行编码,是一开始没有认真想的,所以,一开始觉得无法做出,但是在经过多次对哈夫曼树结点结构进行分析后,可以将初步的能编码的代码写出来。
译码,虽然说是编码的反过程,但是还是有不同的地方的。
译码是先将输入的码文存入一个数组。
然后从数组头开始读码文,这时候哈夫曼树的指针应该停留在根结点。
若读到的数组中的元素是0,则将指针指到根结点的左孩子,然后把根结点的指针也指到左孩子:
反之,若读到的数组中的元素是1,则将指针指到根结点的右孩子,然后把根结点的指针也指到右孩子,如此递归,直到根结点的左、右孩子为-1(初始化的值)为止,这时根指针所指的结点的data就要译出来的字符。
到此,如果码还没有读完,则返回哈夫曼树的根继续译码;
若读完毕,则返回;
若没有哈夫曼树的指针还没有读到叶结点而码已经读完了,则输出“输入的码有错”。
根据对哈夫曼结点的分析,仍然,由于,结点中的该结点的lchild、rchild、parent都是下标,所以各项成员是可以灵活运用的。
将bits[]中的代码读出,倒过来读,所得的编码就是该叶子内存放的字符的编码。
循环将叶子结点依次遍历,将编码输出。
5.用户操作设置,处理,完善
在此,本可以将所有代号放入一个switch()操作。
但是,在对于所写的这个代码,只能完成一个选择,也就是说,当输入选择操作的时候,如果选择了1,创建好树后,就结束了,不会提示可以继续选择操作。
所以,还是使用if语句,通过条件语句判断,操作的代码,并执行相应的操作。
比如if(op=2),检验哈夫曼树是否建立成功,则执行
myhfTree->
postorder(Treeroot);
来使用类方法中的postorder()函数来后序遍历哈夫曼树,检验树是否建立成功。
5系统特色及关键技术
在这个系统中,比较出彩的地方没有多少。
自己觉得比较突出的地方是:
对哈夫曼树结点结构和码结点结构的定义及初始化;
在建哈夫曼树以及编码译码过程中,循环的使用;
主函数中,对界面设计,以及从功能代号的选择所进行的操作,完成这项工作所使用的if语句。
对哈夫曼结点和码结点结构的定义初始化
我觉得好的地方就是定义的lchild,rchild,parent,是整型的,可以做为数组的下标,之后将可以方便灵活的使用。
对lchid,rchild,parent的初始化,方便后面进行的判断。
建立哈夫曼树以及编码译码过程中,循环的使用
循环的使用,while循环与for循环的配合使用,可以将对每个结点的父亲孩子进行寻访与所有结点的寻访独立又结合起来。
主函数中,对界面设计
在主函数中,通过对各项操作分配代号,将要进行的操作以简便的符号代替,方便操作。
通过if语句将所选择的代号进入相应的函数进行相应的操作,并且每个选择后还可以继续进行想要的操作。
6结论
此设计目前可以实现了创建哈夫曼树,以及通过哈夫曼树进行编码和解码。
有待改进之处:
细节不是很注意
心得体会:
通过这次数据结构课程设计,我真的是受益匪浅。
参考文献
[1]侯识忠.数据结构算法VisualC++6.0程序集.中国水利水电出版社,2005.
[2]佟维.实用数据结构.科学出版社,2003:
[3]苏仕华.数据结构解析.习题.课程设计.中国科学技术大学出版社,2009.9:
.
[4]严蔚敏.数据结构:
c语言版.清华大学大学出版社,: