版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
..实验六:二叉树及其应用一、实验目的 树是数据结构中应用极为广泛的非线性结构,本单元的实验达到熟悉二叉树的存储结构的特性,以及如何应用树结构解决具体问题。二、问题描述首先,掌握二叉树的各种存储结构和熟悉对二叉树的基本操作。其次,以二叉树表示算术表达式的基础上,设计一个十进制的四则运算的计算器。如算术表达式:a+b*<c-d>-e/f三、实验要求如果利用完全二叉树的性质和二叉链表结构建立一棵二叉树,分别计算统计叶子结点的个数。求二叉树的深度。十进制的四则运算的计算器可以接收用户来自键盘的输入。由输入的表达式字符串动态生成算术表达式所对应的二叉树。自动完成求值运算和输出结果。四、实验环境 PC微机 DOS操作系统或Windows操作系统 TurboC程序集成环境或VisualC++程序集成环境五、实验步骤1、根据二叉树的各种存储结构建立二叉树;2、设计求叶子结点个数算法和树的深度算法;3、根据表达式建立相应的二叉树,生成表达式树的模块;4、根据表达式树,求出表达式值,生成求值模块;5、程序运行效果,测试数据分析算法。六、测试数据1、输入数据:2.2*〔3.1+1.20-7.5/3 正确结果:6.962、输入数据:<1+2>*3+<5+6*7>;正确输出:56七、表达式求值由于表达式求值算法较为复杂,所以单独列出来加以分析:1、主要思路:由于操作数是任意的实数,所以必须将原始的中缀表达式中的操作数、操作符以及括号分解出来,并以字符串的形式保存;然后再将其转换为后缀表达式的顺序,后缀表达式可以很容易地利用堆栈计算出表达式的值。例如有如下的中缀表达式:a+b-c转换成后缀表达式为:ab+c-然后分别按从左到右放入栈中,如果碰到操作符就从栈中弹出两个操作数进行运算,最后再将运算结果放入栈中,依次进行直到表达式结束。如上述的后缀表达式先将a和b放入栈中,然后碰到操作符"+",则从栈中弹出a和b进行a+b的运算,并将其结果d〔假设为d放入栈中,然后再将c放入栈中,最后是操作符"-",所以再弹出d和c进行d-c运算,并将其结果再次放入栈中,此时表达式结束,则栈中的元素值就是该表达式最后的运算结果。当然将原始的中缀表达式转换为后缀表达式比较关键,要同时考虑操作符的优先级以及对有括号的情况下的处理,相关内容会在算法具体实现中详细讨论。2、求值过程一、将原始的中缀表达式中的操作数、操作符以及括号按顺序分解出来,并以字符串的形式保存。二、将分解的中缀表达式转换为后缀表达式的形式,即调整各项字符串的顺序,并将括号处理掉。三、计算后缀表达式的值。3、中缀表达式分解DivideExpressionToItem<>函数。分解出原始中缀表达式中的操作数、操作符以及括号,保存在队列中,以本实验中的数据为例,分解完成后队列中的保存顺序如下图所示:队首队尾其算法思想是:从左往右按一个字节依次扫描原始中缀表达式m_string,碰到连续的数字或小数点就加到string变量str中;如果碰到括号或操作符就将原先的str推入队列,然后将括号或操作符赋予str,再将str推入队列,并将str赋予空值,依次循环进行直到扫描m_string完成。转化为后缀表达式ChangeToSuffix<>函数。将保存在队列中的中缀表达式转换为后缀表达式,并保存在栈中。这个函数也是整个表达式算法的关键,这里需要两个栈stack_A和stack_B,分别在转换过程中临时存放后缀表达式的操作数与操作符。依次从中缀表达式队列que中出列一个元素,并保存在一个string变量str中,然后按以下几方面进行处理:①如果str是"<",则将str推入栈stack_B。②如果str是">",则要考虑stack_B的栈顶是不是"<",是的话就将"<"出栈stack_B;如果不是,则将stack_B出栈一个元素〔操作符,然后将其推入栈stack_A。③如果str是"+"或"-",则要考虑有括号和优先级的情况,如果栈stack_B为空或者栈顶为"<",则将str推入栈stack_B;因为操作符"+"和"-"优先级相同〔谁先出现就先处理谁进栈stack_A,并且低于"*"和"/",所以当栈stack_B不为空并且栈顶不为"<",则依次循环取出stack_B的栈顶元素并依次推入栈stack_A,循环结束后再将str推入栈stack_B。④如果str是"*"或"/",因为它们的优先级相同并且高于"+"和"-",所以如果栈stack_B为空或者栈顶为"+"、"-"或"<"就直接将str推入栈stack_B;否则就将stack_B弹出一个元素并将其推入栈stack_A中,然后再将str推入栈stack_B中。⑤除了上述情况外就只剩下操作数了,操作数就可以直接推入栈stack_A中。注意整个过程中栈stack_B中的元素只能是如下几种:"<"、"+"、"-"、"*"、"/",而最终栈stack_A保存的是后缀表达式。只有操作数和操作符,如下图所示:注意到最后返回的是stack_B而不是stack_A,因为考虑到为了后面的计算方便,所以将其倒序保存在stack_B中〔最后一个while循环。后缀表达式求值Calculate<>函数。剩下的计算后缀表达式就显得非常简单了,依次将倒序的后缀表达式stack_B弹出一个元素推入保存结果的double类型的栈stack中,如果遇到操作符就从栈stack中弹出两元素进行该操作符的运算并将其结果推入到栈stack中,依次循环进行直到栈stack_B为空,最后栈stack只有一个元素即为表达式的结果。八、实验报告要求实验报告应包括以下几个部分:设计算术表达式树的存储结构;实验中采用的是二叉树的的链接存储。结点格式如下:其严格类的定义如下:template<typenameT>classBinarynode//二叉树的结点类{ public: Binarynode<>:left<NULL>,right<NULL>{}//默认构造函数 Binarynode<constT&item,Binarynode<T>*lptr=NULL, Binarynode<T>*rptr=NULL>:data<item>,left<lptr>,right<rptr>{}//初始化 Tdata;//结点数据 Binarynode<T>*&Left<>{returnleft;}//取left Binarynode<T>*&Right<>{returnright;}//取right protected: Binarynode<T>*left,*right;};给出二叉树中叶子结点个数算法和树的深度算法描述;叶子结点个数算法:template<typenameT>voidLeafcount<Binarynode<T>*t,int*c>//计算树叶子的个数{//t为构建的树,c用来返回叶子节点个数 if<t>//树不为空 { if<t->Left<>==NULL&&t->Right<>==NULL>//若该结点左右均为空,为叶子结点 { *c=*c+1; } Leafcount<t->Left<>,c>;//左子树递归求叶子结点 Leafcount<t->Right<>,c>;//右子树递归求叶子结点 }}树的深度算法:intDepth<Binarynode<T>*t>//计算树的深度{intlh,rh;//定义左右子树的深度if<!t>return0;//树为空返回0else{ lh=Depth<t->Left<>>;//递归调用,求左子树深度 rh=Depth<t->Right<>>;//递归调用,求右子树深度 if<lh>rh>//判断左右子树哪个更大,更大的深度加1返回其值 returnlh+1; else returnrh+1;}return1;}相应的程序要给出足够的注释部分;参见九、附录,由于在报告中分析的算法,在附录源程序中省略部分注释,以免繁杂。给出程序的测试结果验证各项程序功能如下:进入模块选择进入模块一:进入模块二:四种遍历以先序序列为ABDECF,中序序列DBEAFC为例:求树的叶子结点数和树的深度求表达式的值1、输入数据:2.2*〔3.1+1.20-7.5/3 正确结果:6.962、输入数据:<1+2>*3+<5+6*7>; 正确输出:56九、思考题与实验总结1、分析利用完全二叉树的性质和二叉链表存储有什么不同?分析其优缺点。其实利用完全二叉树的性质的存储本质上是顺序存储,但是又区别于一般的顺序存储,由于树无法在顺序表直接进行存储,所以在描述二叉树的时候规定树从左到右,从上到下依次存储树结点,不存在的结点也要存储,其以0表示,对于完全二叉树来讲,只要知道结点在树中的编号,就能迅速定位该结点,但是由于要存储0来表示空结点,在结点数庞大的时候会有可能浪费空间。最后,它若要增加结点,若新增结点已超出范围,则必须要重新申请空间。而二叉链表存储则是典型链表存储,它要利用指针来指向其左右孩子。如果要查找某一结点,必须从根出发,但是不会像利用完全二叉树的性质存储那样浪费不必要的空间。在增加结点时更容易。综上分析,其优缺点:完全二叉树性质存储:优点,查找结点速度快,易于理解,在结点数少的情况下,存储方便。缺点,存储大量结点可能会浪费大量空间,增加结点复杂。二叉链表存储:优点,增加结点容易,易于存储结点数比较大的树。而且指针灵活的应用,更易与在树上进行复杂的操作。缺点,查找结点必须从根出发,依次遍历。2、增加输入表达式进行语法判错的功能。IsWellForm<>函数。判断原始中缀表达式的括号是否匹配,可以利用栈简单实现,即遇到"<"进栈,遇到">"就从栈中弹出一个元素,直到表达式结束。如果栈为空则表示括号匹配,否则不匹配。其具体实现见附录。下面是程序的试验:3.实验总结实验终于完成了,相对来说难度很大,不过由于这个是数据结构的重中之重,所以花了蛮多的心思的,树的确有很多优点,使得它如此举足轻重,它可以勾勒生活中的方方面面的关系,特别在当今社会数据关系如此复杂的情况下,它独享风光是可以理解的。不过由于它结构复杂多变,所以存储起来就颇为费劲了,这造成了我在实验中吃苦头的主要因素。实验中第一次尝试用VISIO画图表,发现它的确是个画图表的好工具。最后对于实验本身不多说了,比较满意,但是需要进一步了解树,了解编程。十、附录源程序包含三个文件,头文件binarynode.h主要给出了二叉树结点类的定义和表达式二叉树类的定义及其相关函数。头文件bt_algorithm.h主要给出了二叉树的相关基本操作。主程序则包含两个模块,子模块一是基于用户自己构建的二叉树的相关基本操作,包括各种遍历,求二叉树的叶子数和求树的深度。子模块二主要是表达式求值的运算,由用户输入中缀表达式,经过运算直接输出结果。下面给出以上三个文件。binarynode.h//该头文件主要给出二叉树结点的定义和表达式二叉树类及其相关的计算函数#ifdefWIN32#pragmawarning<disable:4786>#endif#include<string>#include<stack>#include<queue>usingnamespacestd;template<typenameT>classBinarynode//二叉树的结点类{ public: Binarynode<>:left<NULL>,right<NULL>{}//默认构造函数 Binarynode<constT&item,Binarynode<T>*lptr=NULL, Binarynode<T>*rptr=NULL>:data<item>,left<lptr>,right<rptr>{}//初始化二叉树 Tdata;//结点数据 Binarynode<T>*&Left<>{returnleft;}//取left Binarynode<T>*&Right<>{returnright;}//取right protected: Binarynode<T>*left,*right;};classExpressionType//表达式二叉树类{public: ExpressionType<>; ExpressionType<stringm_string>; voidoperator=<stringm_string>;//将一个字符串表达式赋予m_string doubleCalculate<>;//计算转换后的后缀表达式的值private: queue<string>DivideExpressionToItem<>;//将原始的中缀表达式中的操作数、操作符以及括号按顺序以//字符串的形式分解出来,然后保存在一个队列中 stack<string>ChangeToSuffix<>;//将队列中表示原始表达式各项的字符串调整顺序,转换成后缀表达式的//顺序,并处理掉括号,然后保存在一个栈中 boolIsWellForm<>;//判断原始表达式中的括号是否匹配,如果匹配返回true,否则返回false。 intSize<>;//返回原始表达式所包含的字节数。private: stringm_string;//存储表示原始中缀表达式的字符串。};ExpressionType::ExpressionType<>//构造函数{ m_string="";}ExpressionType::ExpressionType<stringm_string>{ this->m_string=m_string;}voidExpressionType::operator=<stringm_string>//赋值{ this->m_string=m_string;}intExpressionType::Size<>//中缀表达式包含字节数{ returnm_string.size<>;}boolExpressionType::IsWellForm<>//判断表达式正确与否{ stack<char>stack; intsize=Size<>; charch; for<inti=0;i<size;i++> { ch=m_string.at<i>; switch<ch> { case'<': stack.push<ch>; break; case'>': if<stack.empty<>> returnfalse; else stack.pop<>; break; default:break; } } returnstack.empty<>;}queue<string>ExpressionType::DivideExpressionToItem<>{ queue<string>que; if<!IsWellForm<>>//括号是否匹配 { cout<<"Theoriginalexpressionisnotwell-form.Pleasecheckitandtryagain!"<<endl; returnque; }stringstr="";charch;intsize=Size<>;boolbNumber=false;for<inti=0;i<size;i++>{ ch=m_string.at<i>; switch<ch> { case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9': case'.': bNumber=true; break; case'<': case'>': case'+': case'-': case'*': case'/': bNumber=false; break; default:continue; }if<bNumber>{ str+=ch; if<i==size-1> que.push<str>;}else{ if<str.size<>!=0> que.push<str>; str=ch; que.push<str>; str="";}}returnque;}stack<string>ExpressionType::ChangeToSuffix<>//转化为后缀表达式{ queue<string>que; stack<string>stack_A; stack<string>stack_B; que=DivideExpressionToItem<>;//取得中缀表达式队列 if<que.empty<>> returnstack_B; stringstr; while<!que.empty<>> { str=que.front<>; que.pop<>; if<str=="<"> {stack_B.push<str>; } elseif<str==">"> { while<!stack_B.empty<>&&stack_B.top<>!="<"> { stack_A.push<stack_B.top<>>; stack_B.pop<>; } if<!stack_B.empty<>> stack_B.pop<>; } elseif<str=="+"||str=="-"> { if<stack_B.empty<>||stack_B.top<>=="<"> { stack_B.push<str>; } else { while<!stack_B.empty<>&&stack_B.top<>!="<"> { stack_A.push<stack_B.top<>>; stack_B.pop<>; } stack_B.push<str>; }} elseif<str=="*"||str=="/"> { if<stack_B.empty<>||stack_B.top<>=="+"||stack_B.top<>=="-"|| stack_B.top<>=="<"> { stack_B.push<str>; } else { stack_A.push<stack_B.top<>>; stack_B.pop<>; stack_B.push<str>; } } else stack_A.push<str>;} while<!stack_B.empty<>>//如果stack_B中还有操作符则将其弹出并推入stack_A { if<stack_B.top<>!="<"> stack_A.push<stack_B.top<>>; stack_B.pop<>; } while<!stack_A.empty<>> { stack_B.push<stack_A.top<>>; stack_A.pop<>; } returnstack_B;}doubleExpressionType::Calculate<>{ stack<string>stack_A=ChangeToSuffix<>;//取得后缀表达式 if<stack_A.empty<>> return0; stack<double>stack; stringstr; charch;doubledbl;while<!stack_A.empty<>>{ str=stack_A.top<>; stack_A.pop<>; ch=str.at<0>; switch<ch> { case'+': dbl=stack.top<>; stack.pop<>; dbl+=stack.top<>; stack.pop<>; stack.push<dbl>; break; case'-': dbl=stack.top<>; stack.pop<>; dbl=stack.top<>-dbl; stack.pop<>; stack.push<dbl>; break; case'*': dbl=stack.top<>; stack.pop<>; dbl*=stack.top<>; stack.pop<>; stack.push<dbl>; break; case'/': dbl=stack.top<>; stack.pop<>;if<dbl!=0.000>//除数不为0!!{ dbl=stack.top<>/dbl; stack.pop<>; stack.push<dbl>;}break; default: //将字符串所代表的操作数转换成双精度浮点数 //并压入栈 char*p=<char*>str.begin<>; stack.push<atof<p>>; break; }}returnstack.top<>;}bt_algorithm.h//该头文件主要完成二叉树的定义和基本操作#include<iostream>#include<stack>#include<queue>#include<iomanip>#include"binarynode.h"usingnamespacestd;structElement//结点类型{ Element<>{}; Element<Binarynode<char>*t,intx,intlev>:ptr<t>,xOff<x>,level<lev>{} Binarynode<char>*ptr; intxOff,level;};Binarynode<char>*create<conststring&Pres,conststring&Ins>//构造函数{ Binarynode<char>*root; if<Pres.length<>>0> { root=newBinarynode<char><Pres[0]>; intindex=Ins.find<Pres[0]>; root->Left<>=create<Pres.substr<1,index>,Ins.substr<0,index>>; root->Right<>=create<Pres.substr<index+1>,Ins.substr<index+1>>; } elseroot=NULL; returnroot;}template<typenameT>voidPreOrder<Binarynode<T>*t>//先序遍历{ if<t> { cout<<t->data<<""; PreOrder<t->Left<>>; PreOrder<t->Right<>>; }}template<typenameT>voidInOrder<Binarynode<T>*t>//中序遍历{ if<t> { InOrder<t->Left<>>; cout<<t->data<<""; InOrder<t->Right<>>; }}template<typenameT>voidPostOrder<Binarynode<T>*t>//后序遍历{ if<t> { PostOrder<t->Left<>>; PostOrder<t->Right<>>; cout<<t->data<<""; }}template<typenameT>voidLevelOrder<Binarynode<T>*t>{ queue<Binarynode<T>*>Q; Binarynode<T>*p; if<t> { Q.push<t>; while<!Q.empty<>> { p=Q.front<>; Q.pop<>; cout<<p->data<<""; if<p->Left<>!=NULL> Q.push<p->Left<>>; if<p->Right<>!=NULL> Q.push<p->Right<>>; } }}template<typenameT>voidLeafcount<Binarynode<T>*t,int*c>//计算树叶子的个数{ if<t> { if<t->Left<>==NULL&&t->Right<>==NULL> { *c=*c+1; } Leafcount<t->Left<>,c>; Leafcount<t->Right<>,c>; }}template<typenameT>intDepth<Binarynode<T>*t>//计算树的深度{ intlh,rh; if<!t>return0; else { lh=Depth<t->Left<>>; rh=Depth<t->Right<>>; if<lh>rh> returnlh+1; else returnrh+1; } return1;}main//该文件为主程序,面向用户#include<string>#include"bt_algorithm.h"usingnamespacestd;Binarynode<char>*create<conststring&Pres,conststring&Ins>;voidmain<>{ stringpre,in; intq,m,n,number,c=0;//q为模块选择;m为子模块一输入功能序号;n为子模块而输入功能序号 //number用来标记是否退出子模块;c为树叶子个数 ExpressionTypeexpr;stringexpression;//输入的中缀表达式flag:cout<<"┌────────────────────────────────┐"<<endl;cout<<"│二叉树的操作与应用│"<<endl;cout<<"│姓名:翁恒丛学号:6100410184班级:计算机卓越│"<<endl;cout<<"││"<<endl;cout<<"│1.二叉树基本操作2.表达式求值0.退出程序│"<<endl;cout<<"└────────────────────────────────┘"<<endl;cout<<"选择主模块:";cin>>q;switch<q> { case0: exit<0>; break;case1://模块一 {cout<<"┏━━━━━━━━━━━━━━━━━━━━━━━━━┓"<<endl;cout<<"┃二叉树基本操作┃"<<endl;cout<<"┃1.先序遍历5.叶子结点数┃"<<endl;cout<<"┃2.中序遍历6.树的深度┃"<<endl;cout<<"┃3.后序遍历0.退出子模块一┃"<<endl; cout<<"┃4.层序遍历┃"<<endl;cout<<"┗━━━━━━━━━━━━━━━━━━━━━━━━━┛"<<endl; co
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- AI赋能让你的汇报更出色
- 《含N化合物的硒催化羰基化反应研究》
- 《人力资本积累机理及路径研究》
- 《大米抗氧化肽的复合酶法制备及理化性质研究》
- 《朱丹溪六郁理论学术思想研究》
- 《我国民营控股上市公司高管薪酬对公司绩效的影响研究》
- 《小组工作缓解民办机构教师工作压力研究》
- 产品开发与生产流程优化研究
- 产品质量控制与管理实践研究报告
- 《分数阶高斯随机场中的长记忆性研究》
- 咖啡的微观世界智慧树知到期末考试答案章节答案2024年成都师范学院
- 2024-2030年国内工业用金属桶行业市场发展分析及发展前景与投资机会研究报告
- DZ/T 0462.9-2023 矿产资源“三率”指标要求 第9部分:盐湖和盐类矿产(正式版)
- 小学生普法教育完整课件
- 60岁以上用工免责协议
- (“双减”作业案例)“鱼米之乡”-一长江三角洲地区(第一课时)
- 湘教版中考地理一轮复习:八年级上、下册知识点考点背诵提纲
- 农牧区劳动力转移就业工作方案
- 《化学制药技术》课件-国内外化学制药发展趋势
- 小收纳 大世界-整+理与收纳智慧树知到期末考试答案章节答案2024年黑龙江幼儿师范高等专科学校
- 2024中国私募股权市场出资人解读报告-执中+招商银行+财联社
评论
0/150
提交评论