表达式求值
首先要知道如何求逆波兰式(后缀表达式):
表达式的表示形式有中缀、前缀和后缀3中形式。中缀表达式按操作符的优先级进行计算(后面代码实现只包括+、-、*、\,小括号),即数学运算。 后缀表达式中只有操作数和操作符。操作符在两个操作数之后。它的计算规则非常简单,严格按照从左到右的次序依次执行每一个操作。每遇到一个操作符,就将前面的两个数执行相应的操作。
由后缀表达式计算中缀表达式原理:计算机处理后缀表达式求值问题是比较方便的,即将遇到的操作数暂存于一个操作数栈中,凡是遇到操作数,便从栈中pop出两个操作数,并将结果存于操作数栈中,直到对后缀表达式中最后一个操作数处理完,最后压入栈中的数就是后最表达式的计算结果。
中缀表达式转换为等价的后缀表达式
中缀表达式不方便与计算机处理,通常要讲中缀表达式转换为一个与之等价的后缀表达式。等价是指两个表达式的计算顺序和计算结果完全相同。
中缀表达式:0.3/(5*2+1)# 的等价后缀表达式是:0.3 5 2 * 1 + /#
仔细观察这两个等价的表达式可知,操作数的出现次序是相同的,但运算符的出现次序是不同的。在后缀表达式中,运算符的出现次序是实际进行操作的次序;在中追表达式中,由于受到操作符的优先级和括号的影响,操作符出现次序与实际进行操作的次序很可能是不一样的。
算法描述:
将中缀表达式转换为等价的后缀表达式的过程要使用一个栈放“(”,具体可以按照下面的方式进行。
(1)从左到右依次扫描中缀表达式的每一个字符,如果是数字字符和圆点“.”则直接将它们写入后缀表达式中。
(2)如果遇到的是开括号“(”,则将它们压入一个操作符栈(不需要与栈顶操作符相比较),它表明一个新的计算层次的开始,在遇到和它匹配的闭括号“)”时,将栈中的元素弹出来并放入后缀表达式中,直到栈顶元素为“(”时,将栈顶元素“(”弹出(不需要加入后缀表达式),表明这一层括号内的操作处理完毕。
(3)如果遇到的是操作符,则将该操作符和操作符栈顶元素比较:
1、当所遇到的操作符的优先级小于或等于栈顶元素的优先级时,则取 出栈顶元素放入后缀表达式,并弹出该栈顶元素,反复执行直到当前操作符的优先级大于栈顶元素的优先级小于;
2、当所遇到的操作符的优先级大于栈顶元素的优先级的时则将它压入栈中。
(4)重复上述步骤直到遇到中缀表达式的结束符标记“#”,弹出栈中的所有元素并放入后缀表达式中,转换结束
——copy from 飘过的小牛
得到后缀表达式,就不难计算表达式的值,如果是数字压入栈,当遇到运算符,弹出两个(双目)数字计算后再压栈,答案就是栈剩下的最后一个数字
代码:
/* 表达式求值,逆波兰式(后缀表达式)算法 输入(可以有空格,支持小数,实现'+-/*%'): ((1+2)*5+1)/4= 注意:取模一定是要整型,实现版本数字全是double,强制类型转换可能倒置错误 转换为后缀表达式: 得到:1 2 + 5 * 1 + 4 / = 计算后缀表达式:得到:4.00 */ struct Exp { stack<char> op; stack<double> num; bool error; int prior(char ch) { //运算符的优先级 switch (ch) { case '+': case '-': return 1; case '*': case '%': case '/': return 2; default: return 0; } } bool is_digit(char ch) { return '0' <= ch && ch <= '9'; } string get_postfix(string s) { //中缀表达式转变后缀表达式 while (!op.empty ()) op.pop (); op.push ('#'); string ret = ""; int len = s.length (), i = 0; while (i < len) { if (s[i] == ' ' || s[i] == '=') { i++; continue; } else if (s[i] == '(') { op.push (s[i++]); } else if (s[i] == ')') { while (op.top () != '(') { ret += op.top (); ret += ' '; op.pop (); } op.pop (); i++; } else if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || s[i] == '%') { while (prior (op.top ()) >= prior (s[i])) { ret += op.top (); ret += ' '; op.pop (); } op.push (s[i++]); } else { while (is_digit (s[i]) || s[i] == '.') { ret += s[i++]; } ret += ' '; } } while (op.top () != '#') { ret += op.top (); ret += ' '; op.pop (); } ret += '='; return ret; } double cal(double a, double b, char ch) { if (ch == '+') return a + b; if (ch == '-') return a - b; if (ch == '*') return a * b; if (ch == '%') return (int)((int)a % (int)b); if (ch == '/') { if (b != 0) return a / b; error = true; return 0; } } double solve(string str) { //计算后缀表达式 string s = get_postfix (str); while (!num.empty ()) num.pop (); error = false; int len = s.length (), i = 0; while (i < len) { if (s[i] == ' ' || s[i] == '=') {i++; continue;} else if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || s[i] == '%') { double a = num.top (); num.pop (); double b = num.top (); num.pop (); num.push (cal (b, a, s[i])); i++; } else { double x = 0; while (is_digit (s[i])) { x = x * 10 + s[i] - '0'; i++; } if (s[i] == '.') { double k = 10.0, y = 0; i++; while (is_digit (s[i])) { y += ((s[i] - '0') / k); i++; k *= 10; } x += y; } num.push (x); } } return num.top (); } }E; int main(void) { ios::sync_with_stdio (false); //如果全用流的话,加这句话能跑快点 int T; cin >> T; string str; getline (cin, str); while (T--) { getline (cin, str); cout << E.get_postfix (str) << endl; cout << fixed << setprecision (6) << E.solve (str) << endl; } return 0; }
拿着这个模板就可以解决:NYOJ_467_中缀式变后缀式,NYOJ_35_表达式求值,HDOJ_1237_简单计算器,以及ZSTOJ_4189_逻辑运算 ('!'是单目运算符)