支线任务-2
这次的题目来到了Basic Calculator上面。顾名思义,就是实现一个基本的计算器的功能。我们来看看具体的
题目:
Implement a basic calculator to evaluate a simple expression string.
The expression string may contain open (
and closing parentheses )
, the plus +
or minus sign -
, non-negative integers and empty spaces .
You may assume that the given expression is always valid.
翻译:实现一个基本的计算器用来计算简单的字符串表达式。表达式中可能包含左右括号,加减符号,正整数和空格,你可以认为表达式总是可计算的。
样例:
"1 + 1" = 2 " 2-1 + 2 " = 3 "(1+(4+5+2)-3)+(6+8)" = 23
读完题目首先感觉这是个有意思的题目,为什么呢?因为这是很多初学算法的人都会接触到的跟生活非常接近的而且非常实用的应用!不像很多算法题在现实中用处不大。而这个题目我们完成了题目后不但是为了做题,在自己使用的时候也能派上用场呢。
题外话不多说了,就题目本身来说确实是一个非常提高编程实现能力和堆栈理解能力的一个题目。所以有必要把他做的尽量更加完善(超出这个题目的要求)。
思路:
实际上在课堂上老师已经讲了很好的思路了,我凭借自己的理解做法如下:
因为表达式本身就是一颗“运算树”(逆波兰表达式可以认为是这棵树的后序遍历,波兰表达式是其先序遍历而普通的表达式则是这棵树的中序遍历),如下图,而树的结构本身就和栈这种数据结构有着天然的相性(其实和队列也是有这种相性,这要取决于遍历的方式(BFS/DFS)),所以自然的想到要采用栈这种数据结构来辅助计算。
但是针对运算符的优先性是需要有一个输入的,也就是要有办法让程序知道这个优先级。具体的方法也就是按照课堂上说的打出一张表格来。
其中大于小于号其实只是一个规定,具体算法如下:
- 在字符串结尾处添加一个'#'用以确定字符串结束。并且在栈中压入'#',作用既能防止过度弹栈也能控制程序终止。
- 每次拿到一个运算符的时候与栈顶的运算符优先级进行对比,如果大的话就压栈,如果小的话就直到栈顶运算符优先级大于等于这个运算符为止持续弹栈,最后如果优先级相等就弹出这个运算符,大的话就压栈。
- 每次弹出一个运算符就把数字栈的栈顶两个元素做该运算再将结果压回去。
- 直到栈空的时候返回栈顶(此时栈中只应有一个)数字,为所得结果。
不妨在纸上拿(1+3)*2来模拟一下就知道具体是怎么回事了
提高:
当然了,实际上的计算中还有许多其他的操作(除开加减乘除这种基本四则运算),比如下图(是来自某不愿透露姓名的M某语言,据说用来做电原作业很方便;-])
那么我们怎么把它们也纳入这个系统中来呢?
我们这里用乘方这个运算来举例。
首先,乘方就优先级来说比加减乘除都要高,这也就意味着乘方运算符在入栈的时候加减乘除运算符都不能先出栈,但是乘方的优先级依然比左括号要小(事实上,只有右括号能把左括号解救出来,真是浪漫)。
其次,加减乘除入栈的时候也就意味着乘方活的时间不长了,比如2^3+1在+入栈的时候说明^应该先算好,所以它们入栈的时候乘方总是要先出来。
同时,由于乘方是二元运算符,所以没有运算符能与之匹配(也就是没有运算符优先级与它相同)。
这样我们就能把之前的表扩充起来了!
按照这个表做出来的程序就能够正确运行带有乘方的表达式啦!!
具体加入乘方操作的代码会贴在最后。
结论:
按照上述方法就可以定制属于自己的表达式求值,比如可以考虑设计一个逻辑表达式的求值算法之类的 :-) 。
(虽然题目中明确说了不让用eval函数,明明很好用的呢。。。,以下是来自不愿意透露姓名的p某语言的截图)
其实还有个问题,例如 1-(-(3-5)) 这个表达式,如果按照上面的方法做会跪,原因是其中的一个减号其实是负号(一元运算符)!所以其实有个更高的提高要求就是加入一元运算符的表达式求值!有兴趣的读者可以自己尝试实现~
代码:
#include <string> #include <stack> #include <iostream> #include <cmath> using namespace std; int trans(char sign) { int res = 0; switch (sign) { case '+': res = 0; break; case '-': res = 1; break; case '*': res = 2; break; case '/': res = 3; break; case '^': res = 4; break; case '(': res = 5; break; case ')': res = 6; break; case '#': res = 7; break; } return res; } int calc(int a, int b, int sign) { int res = 0; switch (sign) { case 0: res = a + b; break; case 1: res = a - b; break; case 2: res = a * b; break; case 3: res = a / b; break; case 4: res = pow(a, b); break; } return res; } int calculate(string s) { // + - * / ( ) # int map[8][8] = { 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, -1, -1, -1, -1, -1, -1, 0, 0, }; // 2 stacks, one of numbers and the other of sign stack<int> nums; stack<int> signs; s = s + "#"; signs.push(7); bool justSign = false; for (int value = 0, i = 0; i < s.size(); i++) { if (s[i] >= '0' && s[i] <= '9') { value = value * 10 + (s[i] - '0'); justSign = false; } else if (s[i] == ' ') { continue; } else { int sign = trans(s[i]); if (!justSign) { nums.push(value); value = 0; } if (map[signs.top()][sign] < 0) { signs.push(sign); } else { while (map[signs.top()][sign] > 0) { int preSign = signs.top(); signs.pop(); int a = nums.top(); nums.pop(); int b = nums.top(); nums.pop(); nums.push(calc(b, a, preSign)); } if (map[signs.top()][sign] == 0) { signs.pop(); } else { signs.push(sign); } } justSign = true; } } return nums.top(); } int main() { string s; cout << "Please enter an expression: "; getline(cin, s); cout << "The result is: " << calculate(s) << endl; return 0; }