计算中缀表达式”可以称得上是一个特别经典的关于栈的算法题,几乎在所有数据结构教材中都会涉及,而且很多公司面试或者笔试的时候都会把这道题作为一个考察点。可以说,这是一道必须要掌握的算法题。中缀表达式、后缀表达式等概念在这里就不赘述了,让我们直奔主题。
题目:输入一个中缀表达式,计算其结果。
输入的前提假设:
(1)只考虑+、-、*、/这四种运算符,中缀表达式中只有一种括号:();
(2)输入的中缀表达式中只有整数,没有小数;
(3)假定输入是合法的。
很多文章或课本喜欢一步到位,直接讨论如何从中缀表达式计算结果。但对于初学者来说,跨度未免大了点。这里循序渐进,从易到难,先讨论如何将中缀表达式转化为后缀表达式,再讨论如何计算后缀表达式。最后在前面两步的基础上,讨论如何一步到位,直接计算中缀表达式的结果:
一、如何将中缀表达式转化为后缀表达式
在日常应用中,算术表达式中运算符总是出现在两个操作数之间,例如5*(7-2*3)+8/2,这种形式称为中缀表达式。计算一个中缀表达式需要知道运算符的优先级和结合性。乘除是高优先级,加减是低优先级,优先级相同时他们都是左结合的,也就是从左计算到右。有括号就要计算括号内的表达式。
中缀表达式利于人的理解,但不便于计算机的处理。因此需要将中缀表达式转换成后缀表达式,以方便计算机处理。所谓后缀表达式就是将运算符放在运算数之后。后缀表达式也称为逆波兰表达式。
比如:
中缀表达式为:1+(2-3)*4+4/2
对应后缀表达式为:1 2 3 - 4* + 4 2 / +
如何将一个中缀表达式转化为后缀表达式?我们需要借助栈的力量,用它来存放运算符。算法流程如下:
首先将各种运算符(包括括号)的优先级排列如下(数字越大,优先级越高):
1:(
2:+ -
3:* /
4:)
对输入的中缀表达式从左到右遍历:
1)如果遇到数字,直接添加到后缀表达式末尾;
2)如果遇到运算符+、-、*、/:
先判断栈是否为空。若是,则直接将此运算符压入栈。若不是,则查看当前栈顶元素。若栈顶元素优先级大于或等于此操作符级别,则弹出栈顶元素,将栈顶元素添加到后缀表达式中,并继续进行上述判断。如果不满足上述判断或者栈为空,将这个运算符入栈。要注意的是,经过上述步骤,这个运算符最终一定会入栈。
3)如果遇到括号:
如果是左括号,直接入栈。如果是右括号,弹出栈中第一个左括号前所有的操作符,并将左括号弹出。(右括号别入栈)。
4)字符串遍历结束后,如果栈不为空,则弹出栈中所有元素,将它们添加到后缀表达式的末尾,直到栈为空。
二、计算后缀表达式
后缀表达式的计算就相当简单了。准备一个数字栈。从左到右扫描后缀表达式,如果是数字,放入数字栈。如果是符号,从数字栈中弹出两个数字,第一个取出的数字为右运算数,第二个为左运算数,进行运算。然后将结果放进数字栈中。如此反复,直到读完整个表达式后,留在数字栈中的那个数字就是最终结果。
C++代码如下,要注意,下面的代码默认中缀表达式中所有数字都是整数,并且都在0到9之间。而且计算结果都是整数(比如5/2=2)。
#include<iostream> #include<string> #include<stack> using namespace std; int getPriority(char ch) { //获取优先级 if (ch == ‘(‘) return 1; else if (ch == ‘+‘ || ch == ‘-‘) return 2; else if (ch == ‘*‘ || ch == ‘/‘) return 3; else return 4; } string getPostfixExpression(string str) { //将中缀表达式转化为后缀表达式 //默认输入是合法的 stack<char> mystack; int size = str.size(); int i = 0; char tmp; string res = ""; while (i < size) { if (str[i] >= ‘0‘ && str[i] <= ‘9‘){ res.push_back(str[i]); } elseif (str[i] == ‘+‘ || str[i] == ‘-‘ || str[i] == ‘*‘ || str[i] == ‘/‘) { if (mystack.empty()) { mystack.push(str[i]); } else { while (!mystack.empty()) { tmp = mystack.top(); if (getPriority(tmp) >= getPriority(str[i])) { //弹出栈顶元素 res.push_back(tmp); mystack.pop(); } else break; } mystack.push(str[i]); } } else { if(str[i]==‘(‘) mystack.push(str[i]); else { while (mystack.top() != ‘(‘) { tmp = mystack.top(); res.push_back(tmp); mystack.pop(); } mystack.pop(); } } i++; } //遍历完后,若栈非空,弹出所有元素 while (!mystack.empty()) { tmp = mystack.top(); res.push_back(tmp); mystack.pop(); } return res; } int calculator(string str) { //计算后缀表达式的值,默认中缀表达式所有数字都是一位的,在0-9之间 stack<int> mystack; int size = str.size(); int num1, num2, num3; for (int i = 0; i < size; i++) { if (str[i] >= ‘0‘ && str[i] <= ‘9‘) { mystack.push(str[i] - ‘0‘); } else { num2 = mystack.top(); mystack.pop(); num1 = mystack.top(); mystack.pop(); if (str[i] == ‘+‘) { num3 = num1 + num2; } else if (str[i] == ‘-‘) { num3 = num1 - num2; } else if (str[i] == ‘*‘) { num3 = num1 * num2; } else if (str[i] == ‘/‘) { num3 = num1 / num2; } mystack.push(num3); } } return mystack.top(); } int main() { string str="1+(2-3)*4+4/2"; cout <<"中缀表达式为:"<< endl << str << endl; string res = getPostfixExpression(str); cout <<"后缀表达式为:"<< endl << res << endl; int num_res = calculator(res); cout <<"计算结果:"<< endl << num_res << endl; system("pause"); return 0; }
三、直接计算中缀表达式
其实将前面的两步结合起来,就可以得到直接计算的方法。准备一个数字栈和一个符号栈。
从左到右遍历中缀表达式。如果遇到数字,入数字栈。
如果遇到符号(四个运算符以及括号),跟前面的“中缀表达式转后缀表达式”过程一样,对符号栈进行处理。处理过程中,对每一个出栈的运算符:+ - * /,根据“计算后缀表达式”的方法,计算结果(跟数字栈配合)。
如果遍历完中缀表达式后符号栈还非空,就继续出符号栈的运算符,计算,直到符号栈为空。最后数字栈剩下的数字就是结果。
下面给出用C++实现“计算中缀表达式”的代码,里面考虑了“数字不止1位”,并且用浮点型来表示最终运算结果。要求中缀表达式中只能包含整数和运算符(不能包含小数),并且是合法的。
#include<iostream> #include<string> #include<stack> using namespace std; int getPriority(char ch) { //获取优先级 if (ch == ‘(‘) return 1; else if (ch == ‘+‘ || ch == ‘-‘) return 2; else if (ch == ‘*‘ || ch == ‘/‘) return 3; else return 4; } void calculate(stack<double> &mystack, char operation) { double num1, num2, num3; num2 = mystack.top(); mystack.pop(); num1 = mystack.top(); mystack.pop(); if (operation == ‘+‘) { num3 = num1 + num2; } else if (operation == ‘-‘) { num3 = num1 - num2; } else if (operation == ‘*‘) { num3 = num1 * num2; } else if (operation == ‘/‘) { num3 = num1 / num2; } mystack.push(num3); } double calculator(string str) { //计算中缀表达式,默认输入是合法的 stack<double> mystack_number; stack<char> mystack_operation; int i = 0, j; int size = str.size(); char tmp_operation; string tmp_num; while (i < size) { if (str[i] >= ‘0‘ && str[i] <= ‘9‘) { j = i; while (j < size && str[j] >= ‘0‘ && str[j] <= ‘9‘) { j++; } tmp_num = str.substr(i, j - i); mystack_number.push(atoi(tmp_num.c_str())); i = j; } else if (str[i] == ‘+‘ || str[i] == ‘-‘ || str[i] == ‘*‘ || str[i] == ‘/‘) { if (mystack_operation.empty()) { mystack_operation.push(str[i]); } else { while (!mystack_operation.empty()) { tmp_operation = mystack_operation.top(); if (getPriority(tmp_operation) >= getPriority(str[i])) { //计算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } else break; } mystack_operation.push(str[i]); } i++; } else { if (str[i] == ‘(‘) mystack_operation.push(str[i]); else { while (mystack_operation.top() != ‘(‘) { tmp_operation = mystack_operation.top(); //计算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } mystack_operation.pop(); } i++; } } //遍历完后,若栈非空,弹出所有元素 while (!mystack_operation.empty()) { tmp_operation = mystack_operation.top(); //计算 calculate(mystack_number, tmp_operation); mystack_operation.pop(); } return mystack_number.top(); } int main() { string str = "1+(2-3)*4+10/2+2*2+2+2/5"; cout << "中缀表达式为:" << endl << str << endl; double num_res = calculator(str); cout << "计算结果:" << endl << num_res << endl; system("pause"); return 0; }
相信通过这篇文章,大家对这个问题会有所了解。
给出一道思考题:如果加入乘方‘^‘,应该如何处理?要注意,乘方运算是右结合的。
其实很简单。只有两处修改:
1)将乘方添加到优先级中:
1:(
2:+ -
3:* /
4:^
5:)
2)在读中缀表达式的时候,如果读到乘方^,就将它放进符号栈中。因为乘方的优先级是最高的,而且是右结合的,所以无论它前面出现的是什么运算,这些运算都不能执行。而且它本身能否执行也是不知道的,因此只能进栈。
【参考资料】
https://blog.csdn.net/sinat_27908213/article/details/80273557
2019-02-13
23:09:01
原文地址:https://www.cnblogs.com/zhengxunjie/p/10372329.html