完整代码在最后~~
1 需求分析
1.1 问题描述
表达式求值是程序设计语言编译中的一个最基本问题,就是将一个表达式转化为逆波兰表达式并求值。具体要求是以字符序列的形式从终端输入语法正确的、不含变量的整数表达式,并利用给定的优先关系实现对算术四则混合表达式的求值,并演示在求值过程中运算符栈、操作数栈、输入字符和主要操作变化过程。
要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能正确解释表达式。任何一个表达式都是由操作符(operand)、运算符(operator)和界限符(delimiter)组成,我们成它们为单词。一般的,操作数既可以是常数,也可以是被说明为变量或常量的标识符;运算符可以分为算术运算符、关系运算符和逻辑运算符3类;基本界限符有左右括号和表达式结束符等。为了叙述的简洁,我们仅仅讨论简单算术表达式的求值问题。这种表达式只包括加、减、乘、除4种运算符。
人们在书写表达式时通常采用的是“中缀”表达形式,也就是将运算符放在两个操作数中间,用这种“中缀”形式表示的表达式称为中缀表达式。但是,这种表达式表示形式对计算机处理来说是不大合适的。对于表达式的表示还有另一种形式,称之为“后缀表达式”,即将运算符紧跟在两个操作数的后面。这种表达式比较适合计算机的处理方式,因此要用计算机来处理、计算表达式的问题,首先要将中缀表达式转化成后缀表达式,又成为逆波兰表达式。
1.2 问题要求
要求设计并编写一程序,选择适当的数据结构,解决上述问题,要求:
(1)输入输出界面友好;
(2)程序可读性强;
(3)程序具有较强的健壮性;
(4)读入原表达式并创建对应二叉树
(5)对二叉树进行前序遍历、中序遍历、后序遍历(非递归)
(6)输出逆波兰表达式
(7)正确输出最终表达式运算的结果
1.3 设计思路
为了实现表达式求值,可以首先读入原表达式(包括括号)并创建对应二叉树,其次对二叉树进行前序遍历、中序遍历、后续遍历(非递归),并输出逆波兰表达式,最后求解原表达式的值,同时对非法表达式格式能予以判断。
假设输入的表达式为ch=1+2*(3-2)-2/1;Tree是一个储存树根节点的栈,再用一个栈Optr存储operand(操作符),遇到数字就创建以该数字为根的树,并且它的左孩子和右孩子都置为空,然后放到Tree栈中。遇到operaor则将其与栈顶元素比较优先级,决定是否送到输出串中,这样处理之后能得到一个后续的排列,再建立二叉树,当遇到ab+这样的建立左右接点为a和b的根为+的树并返回根接点,当遇到操作符前面只有一个操作数的如c*这样的情况就建立右子接点为c的根接点为*的,左结点为上次操作返回的子树的根接点(这种情况下就是合并树的情况)。
非递归方法进行树的遍历需要用到栈结构。首先需要建立一个空栈(栈元素的类型是指向树结点的指针)。
首先将根节点压入栈中。然后进入循环。
如果栈不空,
弹出栈顶元素,
进行访问,
压入右子树(节点指针),
压入左子树(节点指针),
循环。
最后对表达式求值,可直接利用前面输出的逆波兰表达式来求值。遇到数字就直接进数栈,遇到操作符就与操作符栈的栈顶元素比较优先级,如果优先级大于栈顶元素,则将该操作符入栈,并读取表达式下一位。否则如果优先级小于栈顶元素,则将操作符栈顶的元素(theta)出栈,并将数字栈出栈两个数字a、b,在通过运算函数Operate(a,theta,b)把运算结果算出并重新压入数字栈,并读取表达式下一位。如果优先级相等,则将操作符栈顶元素出栈,并继续读取表达式下一位。直至表达式被读取完,此时数字栈顶储存的数字便是表达式的计算结果了,通过GetTop函数便可读取到运算结果了。
2详细设计
2.1抽象数据类型定义
ADTStack{
数据对象:D={ai|ai∈ElemSet,i=1,2,...,n,n≥0}
数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,...,n}
约定an端为栈顶,a1端为栈底。
基本操作:栈的初始化、进栈、出栈、获取栈顶元素等
}ADT Stack
ADTBinaryTree{
数据对象:D是具有相同特性的数据元素的集合
数据关系R:If D=Φ,then R=Φ;
IfD≠Φ,thenR={H};
①root唯一
②子树不相交
③关于数据元素的说明
④关于左子树和右子树的说明
⑤.........
基本操作:创建二叉树、先序遍历二叉树、中序遍历二叉树、后序遍历二叉树(非递归)等
}ADTBinaryTree
CreateBiTree(&T,definitheb)
初始条件:definition给出二叉树T的定义。
操作结果:按definition构造二叉树T。
PreOrderTraverse(T)
初始条件:二叉树T存在。
操作结果:先序遍历T,对每个结点访问一次。
lnOrderTraverse(T)
初始条件:二叉树T存在。
操作结果:中序遍历T,对每个结点访问一次。
PostOrderTraverse(T)
初始条件:二叉树T存在。
操作结果:后序遍历T,对每个结点访问一次。
2.2存储结构设计
·数据结构分为逻辑结构和存储结构
·逻辑结构与数据的存储没有关系,是独立于计算机的,是从具体问题抽象出来的数学模型。
·存储结构只有顺序存储结构和链式存储结构。
1:栈
顺序栈 (top用来存放栈顶元素的下标)
判断栈S空:如果S->top==-1表示栈空。
判断栈S满:如果S->top==Stack_Size-1表示栈满。
链栈(top为栈顶指针,指向当前栈顶元素前面的头结点)
判断栈空:如果top->next==NULL表示栈空。
判断栈满:当系统没有可用空间时,申请不到空间存放要进栈的元素, 此时栈满。
typedef struct StackNode
{
BiTNode *root;
char op;
struct StackNode *next;
} StackNode,*LinkStack;
我在使用到栈的操作的时候使用的都是链栈的存储结构,因为计算机不知道用户输入的表达式的长度,如果使用顺序储存结构的话无法提前分配储存空间。而使用链栈便可不用提前分配储存空间。并且使用链栈的储存结构还有利于插入,删除,修改操作。
2:二叉树
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
typedef struct BiTNode
{
char data;
int num;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
在使用读取到的表达式来创建二叉树时我使用了链式结构,主要用来让根节点的左指针域指向它的左孩子,让根节点的右指针域指向它的右孩子。不过我构建的树的结构有两个数据域,char类型是用来储存操作符的,int 类型是用来储存数字的,这样构造可以省去后续将大于10的字符型数字转换为整形的麻烦。
2.3算法设计
假设输入的表达式为ch=1+2*(3-2)-2/1;Tree是一个储存树根节点的栈,再用一个栈Optr存储operand(操作符),遇到数字就创建以该数字为根的树,并且它的左孩子和右孩子都置为空,然后放到Tree栈中。遇到operaor则将其与栈顶元素比较优先级,决定是否送到输出串中,这样处理之后能得到一个后续的排列,再建立二叉树,当遇到ab+这样的建立左右接点为a和b的根为+的树并返回根接点,当遇到操作符前面只有一个操作数的如c*这样的情况就建立右子接点为c的根接点为*的,左结点为上次操作返回的子树的根接点(合并树)。
最后对表达式求值,可直接利用前面输出的逆波兰表达式来求值。遇到数字就直接进数栈,遇到操作符就与操作符栈的栈顶元素比较优先级,如果优先级大于栈顶元素,则将该操作符入栈,并读取表达式下一位。否则如果优先级小于栈顶元素,则将操作符栈顶的元素(theta)出栈,并将数字栈出栈两个数字a、b,在通过运算函数Operate(a,theta,b)把运算结果算出并重新压入数字栈,并读取表达式下一位。如果优先级相等,则将操作符栈顶元素出栈,并继续读取表达式下一位。直至表达式被读取完,此时数字栈顶储存的数字便是表达式的计算结果了,通过GetTop函数便可读取到运算结果了。
图2.3.1(算法流程图1)