题目链接:
简单计算器
题目描述:
1 + 2 4 + 2 * 5 - 7 / 11 0
3.00 13.36
解析:
这道题典型的用栈模拟的习题,我们可以建立两个栈,一个用来存放字符,另外一个用来存放数字,一开始的时候,两个栈都是空的,然后我们将输入的字符串从头到尾一直遍历过去,数字和运算符不断入栈,当检测到当前遍历到的运算符比栈顶的运算符优先级更低时,或者当前遍历到的运算符比栈顶的运算符优先级同为高级(*或/),栈顶那么说明此时栈顶的运算符需要计算了,所以从数字栈中取出两个数进行运算,运算后将结果再压入数字栈中,并且将运算符栈的栈顶元素作出栈处理。注意:从数字栈中取出两个数,如果是作除法运算,应该返回值是b/a,减法运算也一样,返回值是b - a;原因是b比a先入栈,说明b在表达式的前面,而减法运算和除法运算是不满足交换律的。然后重复上述运算,直到遍历完毕。
遍历结束之后,那么就结束运算了吗?很明显是没有的,比如说计算:
1 * 2 - 4 + 5;
由于运算符优先级的问题,那么这个表达式一开始遍历到第二个运算符‘-’号时,发现栈顶‘*’运算符的优先级更高,所以取出两个数进行乘法运算,然后将结果压入数字栈,而后再往后遍历,直到遍历结束之后,都没有再进行运算。那么此时
数字栈中从栈底到栈顶依次有2,4,5;
运算符栈从栈底到栈顶依次有-,+;
而对应的表达式是2-4+5(很明显我们口算得结果为3).
那么在for循环遍历结束后,我们要判断一下运算符栈是否为空,不为空则继续运算。
这时候,你可能会想,继续按照上述思路计算不就行了,但是事实真的如此吗?
我们可以试着算一算,第一步是计算4+5 = 9,然后9入栈(此时数字栈中剩下2和9,运算符栈中剩下‘-’号运算符),然后再是第二步,计算2 - 9 = -7,然后判断得运算符栈为空,那么此时输出数字栈栈顶元素即为答案,而这样做的答案却是-7,很明显是错误答案,我们分析一下答案错在了哪里。
首先,综合一下计算得过程,算得-7的答案的表达式为 2-(4+5) = -7,而我们实际表达式是2-4+5,造成上述错误答案的原因就是运算符优先级出现了问题,2-(4+5) 是先计算了加法,再计算减法,相当于把整个表达式逆序运算了。而2-4+5这个表达式中只含有‘-’和‘+’,同级运算符应当从左向右依次运算,即要把原来逆序运算的错误形式改正。
那么很自然我们就想到了把数字栈和运算符栈颠倒一下即可:
颠倒前:
数字栈中从栈底到栈顶依次有2,4,5;
运算符栈从栈底到栈顶依次有-,+;
颠倒后:数字栈中从栈底到栈顶依次有5,4,2;
运算符栈从栈底到栈顶依次有+,-;
而颠倒之后,再进行逆序运算就相当于原来的栈进行正序运算。(需要注意的是,在颠倒前,原来‘-’号两边的数是b先入栈,a后入栈,所以返回结果是b-a,而颠倒后,b到了a的后面,即颠倒后a更靠近栈底,如果按照写好的运算函数的话,返回值将是a-b,但是正确结果是b-a,所以应该对返回值取相反数)
计算过程:(1)-(4 - 2) = -2
(2)5+(-2) = 3
结果正确,然后我就信心满满的交了,却得到了这样的答案:
于是我就纳闷了,都分析这么透彻了,怎么还是wrong answer?
然后我就开始测试大量的数据,终于发现了一个错误,比如说如下的等式:
1 + 2 - 4 / 8
我们口算得答案为2.50;
如果按照上述规则运算,则在for循环遍历时不会进行任何运算,然后在循环体外先将栈颠倒,得到此时的数字栈和运算符栈为:
数字栈中从栈底到栈顶依次有8,4,2,1;
运算符栈从栈底到栈顶依次有/,-,+;
那么运算过程为:8/4 = 2;-(2-2) = 0;1+0 = 1
很明显,得出了错误答案,我们也得到了错误原因,那就是除法运算也"逆"运算了,而将“逆”运算转“正”是针对同级运算符,在这里我们要先计算更高级的运算符才能翻转栈。根据运算规则:
当检测到当前遍历到的运算符比栈顶的运算符优先级更低时,或者当前遍历到的运算符比栈顶的运算符优先级同为高级(*或/),栈顶那么说明此时栈顶的运算符需要计算了,所以从数字栈中取出两个数进行运算,运算后将结果再压入数字栈中,并且将运算符栈的栈顶元素作出栈处理。
经过分析后我们能够知道,运算符栈中最多存在一个‘*’或者‘/’在遍历中未进行运算,而且一定是位于栈顶。
而有了这样的分析之后,我们在翻转整个栈之前的话,可以先进行判断一下,栈顶是否为'*'或者‘/’,如果是的话,则可以先进行运算,然后再翻转整个栈即可。
经过简单的修改之后,得到了这样的答案:
完整代码实现:
#include<iostream> #include<cstdlib> #include<cstdio> #include<cstring> using namespace std; template <class Type> class Stack{ private: Type *data; int top_index,Maxsize; public: Stack(int m_size){ //构造函数 data = new Type[m_size]; top_index = -1; Maxsize = m_size; } ~Stack(){ //析构函数 delete [] data; } void push(Type element){ if(top_index >= Maxsize-1){ //栈满,插入失败 return; } ++top_index; data[top_index] = element; } void pop(){ if(top_index < 0){ //栈空,出栈失败 return; } --top_index; } Type top(){ //获取栈顶元素 if(top_index > -1){ return data[top_index]; } } bool empty(){ return top_index == -1; } int cnt_num(){ return top_index + 1; } void reverse_stack(Stack <Type> &_stack){ Type temp[210]; int i = 0; while(!_stack.empty()){ temp[i] = _stack.top(); _stack.pop(); //栈顶元素出栈 i++; } for(int j = 0;j < i;j++){ _stack.push(temp[j]); } } }; bool judge_priority(char a,char b){ //判断是否优先级更高 if((a=='*'&&b=='+')||(a=='*'&&b=='-')||(a=='/'&&b=='+')||(a=='/'&&b=='-')){ return true; } return false; } bool judge_equal(char a,char b){ //判断两个运算符优先级是否相等 if((a=='+'&&b=='+')||(a=='-'&&b=='-')||(a=='+'&&b=='-')||(a=='-'&&b=='+')){ return true; } return false; } bool priority(char a,char b){ if(judge_priority(a,b) || judge_equal(a,b)){ return true; } return false; } double calc(Stack <double> &numbers,Stack <char> &operations){ double a = numbers.top(); numbers.pop(); double b = numbers.top(); numbers.pop(); char c = operations.top(); operations.pop(); if(c=='+'){ return b+a; } if(c=='-'){ return b-a; } if(c=='*'){ return b*a; } if(c=='/'){ return b/a; } } bool is_num(char cc){ //判断其是否为数字字符 if(cc >= '0' && cc <= '9'){ return true; } return false; } bool is_operation(char cc){ //判断其是否为'+','-','*','/'操作符 if(cc=='+'||cc=='-'||cc=='*'||cc=='/'){ return true; } return false; } int main() { char str[210]; //前者存放全部字符 while(cin.getline(str,210) &&strlen(str) > 1){ Stack <double> numbers(210); Stack <char> operations(210); for(int i = 0;str[i] != '\0';i++){ if(is_num(str[i])){ double ans = 0; for(;str[i] != '\0';i++){ if(is_num(str[i])){ ans = ans * 10 + str[i] - '0'; } else{ break; } } numbers.push(ans); } else if(is_operation(str[i])){ if(operations.empty() || priority(str[i],operations.top())){ operations.push(str[i]); } else{ numbers.push(calc(numbers,operations)); i = i - 1; //回溯,继续执行下面的语句,则跳转至i++部分 } } } bool tmp = false; while(!operations.empty()){ if(operations.top()!='*'&& operations.top()!='/'&&!tmp) { operations.reverse_stack(operations); numbers.reverse_stack(numbers); tmp = true; } else if(operations.top()=='-'){ numbers.push(-1.0*calc(numbers,operations)); } else{ numbers.push(calc(numbers,operations)); } } printf("%.2f\n",numbers.top()); memset(str,0,sizeof(str)); } return 0; }需要注意的是:这道题用G++提交的时候竟然wrong answer,原因是我改变了cout输出流的输出形式,而用C++提交却Accept,然后我用printf("%.2f\n",numbers.top());时,用G++ 提交才Accept。
附上错误原因,以后警示自己:(具体原因我也不太清楚,知道的可以私信我一下)
cout.setf(ios::fixed); cout<<setprecision(2); cout << numbers.top() << endl; //用G++提交wrong answer //以后还是尽量用printf输出,避免不必要的错误
这样的代码是比较糟糕的,所以我们重新审视一下这道题:
从题干开始,我们是否一开始就要读入全部字符?
为什么题干的输入会隔着一个空格之后再接着运算符?
是否一定需要将整型数字也声明为字符型?
带着以上问题,我们可以先观察一下运算表达式:
4 + 2 * 5 - 7 / 11数字有5个,运算符有4个,当输入0的时候结束输入,那么其实我们可以换一种思路,输入多少读取多少,而不必一下子全部输入,这样的话带来不必要的麻烦。
一开始我们输入并读取第一个数字,然后再判断第一个数字以及紧接着的字符,如果是0和'\n'(回车是表示输入结束的标志),那么跳出循环体即可。然后再看剩下的部分,4个数字+4个运算符,这样的话,其实我们可以两两输入,再读取。
如果读取到‘*’的话,那么我们就可以进行乘法运算,并将结果保存在前一个数字中,除法同理。
而如果读取到‘+’的话,由于运算符优先级的问题,那么我们就需要把‘+’运算符一起读取的数字存储起来,存储在上一位读取到的数字并存储的下一位即可,而'-'号的话,为了最后运算简便,我们将减法运算统一转换成加法运算。(即5-2 = 5 + (-2))那么同样的,将读取到的数字存储其相反数至数组即可。
直到字符读取到‘\n’则表示读取结束。
然后将所有存储好的数相加即可得出答案。
例如上述表达式,运算过程为:
一开始输入num[0],并读取第一个数4和紧接着的字符,发现不满足0和'\n'的条件。因此将4存储后,即num[0] = 4,然后继续输入并读取。
第一步:输入 ‘+’ 和数字2,读取后,发现‘+’运算符,因此将2存储,即num[1] = 2;
第二步:输入 ‘*’ 和数字5,读取后,发现‘*’运算符,因此将与前一个存储的数进行乘法运算然后再存储至前一个数,即num[1] =num[1] * 5,即最后存储得num[1] = 10;
第三步:输入 ‘-’ 和数字7,读取后,发现‘-’运算符,因此将7取反后存储,即num[2] = -7;
第四步:输入 ‘/’ 和数字11,读取后,发现‘/’运算符,因此将与前一个存储的数进行除法运算然后再存储至前一个数,即num[2] =num[2] / 11,即最后存储得num[2] = -0.64;
至此,读取结束。
然后剩下了num[0],num[1],num[2]为各种运算符计算后的结果,再将所有结果相加后即得答案为13.36
完整代码实现:
#include<cstdio> int main(){ double num[100],f; while(scanf("%lf",&num[0])==1){ double ans = 0; char s = getchar(); if(num[0]==0&&s=='\n') break; int i = 0; while(scanf("%c %lf",&s,&f)==2){ if(s=='*') num[i] *= f; else if(s=='/') num[i] /= f; else if(s=='+') num[++i] = f; else num[++i] = -f; if((s=getchar()=='\n')) break; } while(i>=0){ ans += num[i]; i--; } printf("%.2f\n",ans); } return 0; }
优化自己的代码也是一个好的程序员必备的素质。
学会写测试数据也是非常重要的一项技能。
附上该题自己制作的一些测试数据:
1 + 2 * 3 1 + 2 * 0 1 * 2 * 3 1 / 2 / 3 0 + 0 1 + 0 0 + 1 1 * 2 / 3 + 4 * 0 1 * 2 + 3 0 + 0 0 * 0 1 * 0 * 2 * 3 1 + 2 / 3 / 4 1 + 0 / 3 / 4 1 + 2 + 3 + 4 1 + 2 - 4 + 5 - 9 1 + 2 * 3 * 4 1 - 4 / 3 1 - 3 * 4
如有错误,还请指正,O(∩_∩)O谢谢