数据结构实习——算术表达式求值
一、实习题目及要求
1、题目:算术表达式求值
2、要求:
(1)正确解释表达式;
(2)符合四则运算规则:
(3)先乘除、后加减;
(4)从左到右运算;
(5)先括号内,后括号外;
(6)输出最后的计算结果。
二、问题描述
1、对一个合法的中缀表达式求值。
2、假设表达式只包含+、-、*、/ 四个双目运算符,并且允许有括号出现,运算符本身不具有二义性。
三、问题分析
1、本题要求我们计算算数表达式的值,在程序中需要大量的重复运用栈的基本操作。而在程序设计过程需要考虑到运算符的优先级,并需要熟悉栈的操作从而进行计算 。
2、表达式可看做是一个字符串,其中字符分为操作数和运算符两类,因此需要两个工作栈,命名为OPTR(运算符栈)和OPND(操作数栈)。以此判断字符串中的字符,如果是操作数则压入操作数栈,如果是运算符栈,则压入运算符栈,然后再进行弹栈操作。
3、本题还需考虑到小数问题,即把小数点之后的数字和之前的数字分开处理,然后再一同压入栈中。
4、此外还需考虑到负数问题,在输入命令时,负数需要添加括号,通过扫描,当字符串中的字符前一位时‘)’后一位时‘-’时,则需要在操作数栈中先压入一个数0,然后再进行后续的运算,记得实现负数的运算。
四、设计思想
1、本题我首先设计了一个判断运算优先级的函数char Precede(char a, char b)该函数参考老师给的ppt里的代码,并进行了细微的改动。
2、然后定义了函数bool Operate(double a, char theta, double b, double &r),用于计算二元表达式的值,这里需要注意,当除数为零的时候应该返回错误信息,并提醒用户,所以这里我把该函数定义成bool型。
3、在压栈的时候需虑到,字符串中的字符是那种类型,如果是运算符,则需要压进运算栈,如果是操作数,则压进操作数栈,此时,则需要编写一个函数进行字符类型的判断,这里,我编写了一个函数bool IsOperator(char ch),用于判断字符ch是否为运算符,如果是运算符在后面压栈的时候则压进运算符栈,如果不是,而是操作数则压入操作数栈。
4、除了需要判断字符是何种类型之外,还需要判断的是,我们在输入表达式时,输入的括号是否配对,如果配对的话,则可以继续计算,如果不能配对的话,则需提醒用户,为了实现该功能,我便编写了函数bool Check(char s[])用于检查括号是否配对。
接下来则是最重要的,设计具体的计算函数,这里我编写了一个bool Calculate(char s[], double &result) 函数,用于计算表达式结果,在函数的定义过程中,我把函数的类型定义成了bool型,是因为我该函数的编写过程中,我调用了前面编写的bool型的计算二元表达式值得函数,从而在后续函数调用(计算)时能够实现计算并判断函数是否符合规则,即判断当计算除法时,除数是否为0;首先,当遇到小数点得时候,则需要把小数点之后的数字和之前的数字分开处理,用point标记出现小数点后第x位,在继续查找记录数字, 至运算符结束。整数部分为num = num * 10 + (s[j] - 48),即非小数时num = num * 10 + (s[j] - 48);这里由于输入得数属于字符型,根据ascll码把字符串的数减去48即使我们需要计算的数。
然后判断输入的字符是否时运算符,然后根据运算符的优先级进行压栈,然后把操纵数压入操作数栈,再进行弹栈计算,这里,进行是否是负数的判断,如果字符是‘)’且它的后一位是‘-’,则需要往操作数栈里先压入一个0,然后再进行后续计算,则可以实现负数的运算。
5、在主函数中,即要求输入表达式之后,需要转换成规格化表达式,补“/0”和“=”。
五、设计流程图
六、附录
【源代码】
#include "stdafx.h"
#include <cmath>
#include <stack>
#include <string>
#include<iostream>
using namespace std;
//判断运算符优先级
char Precede(char a, char b) { //判断运算符优先级
int i, j;
char Table[8][8] = {
{ ' ', '+', '-', '*', '/', '(', ')', '=' },
{ '+', '>', '>', '<', '<', '<', '>', '>' },
{ '-', '>', '>', '<', '<', '<', '>', '>' },
{ '*', '>', '>', '>', '>', '<', '>', '>' },
{ '/', '>', '>', '>', '>', '<', '>', '>' },
{ '(', '<', '<', '<', '<', '<', '=', ' ' },
{ ')', '>', '>', '>', '>', ' ', '>', '>' },
{ '=', '<', '<', '<', '<', '<', ' ', '=' }
}; //优先级表格
for (i = 0; i < 8; i++)
if (Table[0][i] == a) //寻找运算符a
break;
for (j = 0; j < 8; j++) //寻找运算符b
if (Table[j][0] == b)
break;
return Table[j][i];
}
//计算二元表达式的值
bool Operate(double a, char theta, double b, double &r) { //计算二元表达式的值
if (theta == '+')
r = a + b;
else if (theta == '-')
r = a - b;
else if (theta == '*')
r = a * b;
else {
if (fabs(b - 0.0) < 1e-8) //如果除数为0,返回错误信息
return false;
else
r = a / b;
}
return true;
}
//判断字符ch是否为运算符
bool IsOperator(char ch) { //判断字符ch是否为运算符
char ptr[10] = { '+', '-', '*', '/', '(', ')', '=' };
int i;
for (i = 0; i < 7; i++) {
if (ch == ptr[i])
return true;
}
return false;
}
//检查表达式括号是否匹配
bool Check(char s[]) { //检查表达式括号是否匹配
int flag = 0, i;
for (i = 0; s[i] != 0; i++) {
if (s[i] == '(')
flag++;
if (s[i] == ')')
flag--;
}
if (flag)
return false;
else
return true;
}
//计算表达式的结果
bool Calculate(char s[], double &result) { //计算表达式的结果
char theta;
int i = 0, j, point = 0;
double a, b, r, num = 0;
stack<double> OPND; //数字栈
stack<char> OPTR; //运算符栈
OPTR.push('=');
while (s[i] != '=' || OPTR.top() != '=') { //对表达式a进行计算
if ((s[i] >= '0' && s[i] <= '9') || s[i] == '.') { //字符是数字或者小数点
num = 0; //初始化数字为0
point = 0; //point用来标记是否出现小数点以及当前处于小数点后第x位,point==10^x
if (s[i] == '.')
point = 10;
else
num = s[i] - 48;
j = i + 1;
while (!IsOperator(s[j])) { //继续往后查找并记录该数字,直到该数字结束遇到运算符为止
if (s[j] == '.') {
point = 10;
j++;
continue;
}
if (!point) //整数部分
num = num * 10 + (s[j] - 48);
else {
num = num + 1.0 * (s[j] - 48) / point; //小数部分
point *= 10; //小数位数后移一位
}
j++;
}
i = j;
OPND.push(num); //将该数字压入栈中
}
else if (IsOperator(s[i])) { //字符是运算符
if (s[0] == '-'){//负数
num = 0;
OPND.push(num);
}
else if (s[i] == '('&&s[i + 1] == '-'){//负数
num = 0;
OPND.push(num);
}
switch (Precede(s[i], OPTR.top())) { //该运算符和栈顶运算符进行优先级比较并做相关处理
case '<':
OPTR.push(s[i++]);
break;
case '=':
OPTR.pop();
i++;
break;
case '>':
theta = OPTR.top(); //从栈中弹出一个运算符进行计算
OPTR.pop();
b = OPND.top(); //弹出两个数字,注意顺序,先弹出的数是第二个操作数
OPND.pop();
a = OPND.top();
OPND.pop();
if (Operate(a, theta, b, r)) //计算并判断是否有除数等于0的情况
OPND.push(r); //若正常,则将结果压入栈中
else
return false; //出现除数为0的情况,返回错误信息
break;
}
}
}
result = OPND.top(); //最后数字栈中的数即为表达式的最终结果
return true;
}
int main()
{
int i, j;
char s1[100], s2[100];
double result;
cout << "Please input an expression to enter with the Enter key:" << endl;
cout << "(If you want to calculate negative numbers, add brackets to negative numbers.)" << endl;
while (cin >> s1) { //输入表达式
if (strlen(s1) == 1 && s1[0] == '0')
break;
cout << "The value of this expression is:";
cout << endl;
for (i = 0, j = 0; s1[i] != 0; i++) { //将表达式转换为规格化的表达式,并在末尾加上“=”,保存在s2中
if (s1[i] == ' ')
continue;
s2[j++] = s1[i];
}
s2[j++] = '=';
s2[j] = '\0';
puts(s2);
if (Check(s2)) { //检查括号是否匹配
if (Calculate(s2, result)) //计算并检查表达式中是否出现除数为0的情况
cout << result;
else
cout << "The divisor can not be 0. " << endl;
}
else
cout << "Brackets do not match." << endl;
}
return 0;
}
【测试数据】
1、 3.5×(7+2) /(-6)(包括了+、—、*、/、(、)、小数、负数的运算)
2、 3/0(除数为0时)
3、 1+2×(3+4 (括号不匹配时)
【运行结果】
(1)
(2)
(3)