企业面试高频考点(一):逆波兰表达式
问题背景导入
小明太懒了,他想计算一个式子,但是不想动笔算,例如
1 + 2 * ( 3 - 4 ) / 5 + 6 = ??
你可以动手写一个程序帮小明算出来吗??
概念提出
逆波兰表达式
逆波兰表达式是什么东西??
逆波兰表达式又叫做后缀表达式。逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasiewicz)于1929年首先提出的一种表达式的表示方法 [1] 。后来,人们就把用这种表示法写出的表达式称作“逆波兰表达式”。逆波兰表达式把运算量写在前面,把算符写在后面。
为什么要使用逆波兰表达式??
计算机在计算表达式的时候,由于计算机的特性,表达式所对应的字符对于计算机如同黑盒一般,计算机无法判断优先级,无法进行正确计算,因此必须先把表达式转换成计算机可以从左到右遍历算出的表达式,这个表达式就是逆波兰表达式。
逆波兰表达式的特征
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
逆波兰表达式的规则
一个表达式E的后缀形式可以如下定义:
(1)如果E是一个变量或常量,则E的后缀式是E本身。
(2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1'E2' op,这里E1'和E2'分别为E1和E2的后缀式。
(3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是:ab+
(a+b)*c-(a+b)/e的后缀表达式为:
(a+b)*c-(a+b)/e
→((a+b)*c)((a+b)/e)-
→((a+b)c*)((a+b)e/)-
→(ab+c*)(ab+e/)-
→ab+c*ab+e/-
怎么样转换成逆波兰表达式??
仔细思考:假如我们拿到一个表达式,我们应该如何进行计算??
我们计算表达式的时候当然会先关注哪个先算。找到优先级高的先算,然后再算优先级低的,这很简单。那么我们怎么使用计算机把这个东西表达出来呢??
使用一个栈,我们之前讲到了栈的应用,栈的特点是很好用的,栈底存放的东西有一个特征:我可以把我暂时不用,但是以后必用的东西先保存起来
这个特点是很重要的。例如我入栈 ’+‘。然后下一个操作符是’*‘。这个时候我们可以知道,我 *的优先级必比 +要大,所以我 *号必可以先算,因为没有什么可以在 *号之前先算(这里我们暂时把括号的情况省略,一会儿再考虑,不然会讲的很不清晰)
这个时候我们可以读取下一个操作数,然后进行计算。
具体流程如下:
思考流程图的原因:为什么比栈顶优先级高就入栈,底或者等了才出栈??
因为,如果我比上一个操作数的优先级高的话,那么我入栈,也就是说我的优先级一定比我下面的要高,然后我这个元素出栈的时候是下一个运算符的优先级比目前要低或者相等的时候,这样的话可以保证我这个运算符的左右两个操作数必定是可以计算的,不管是发生什么,一定可以计算,所以我们就出栈。
注意:逆波兰表达式最终一定会有一个符号留着栈里面,所以循环结束了之后必须要把栈里面的符号全部清空出来。
遇到括号怎么办??
- 左括号入栈,因为我之前要求了括号的优先级是最低的,所以这很巧妙,它会导致括号里面的第一个符号必定会入栈。第一个符号入栈之后可以发现后面怎么样就会回归平常了,我们可以理解为进入了一块新大陆,第一个进去了之后后面的没有影响。
- 遇到右括号了之后,马上把夹在左括号和右括号之间的所有运算符全部出栈,因为这个时候我们必须要先把这个括号里面的东西算了,才能算后面的,然后)的优先级也是最低的,可以保证我出了括号之后必定,不管是什么符号都可以进行计算
代码:
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<stack>
#include<unordered_map>
using namespace std;
int main()
{
unordered_map<string, int> m;
//罗列优先级,把优先级记录下来
m["("] = 0;
m[")"] = 0;
m["*"] = 2;
m["+"] = 1;
m["-"] = 1;
m["/"] = 2;
vector<string> res;
while (true)
{
string temp;
cin >> temp;
res.push_back(temp);
if (res.back() == "=")
{
break;
}
}
//res.pop_back();//把最后一个等号字符给删了
vector<string> reverse_polish;
stack<string> st;
auto it = res.begin();
while (*it != "=")
{
if (*it == "+" || *it == "-" || *it == "*" || *it == "/" || *it == "(" || *it == ")")
{
if (*it == "(")//这是特殊情况,要单独处理
{
st.push(*it);//左括号入栈
it++;
continue;
}
if (*it == ")")//只要是右括号,左括号里面的符号必须全部导出,然后把左括号删掉
{
while (st.top() != "(")
{
reverse_polish.push_back(st.top());
st.pop();
}
st.pop();//把左括号删除
it++;//把指针指到下一个
continue;
}
if (st.empty())
{
st.push(*it);//如果栈为空的话直接入栈
it++;
}
else
{
//栈不为空
while (it != res.end() && !st.empty())
{
if (m[*it] > m[st.top()])//如果优先级要更高的话,这个运算符入栈
{
st.push(*it);
it++;
break;//入栈之后就break继续走大循环
}
else
{
string temp = st.top();
st.pop();
reverse_polish.push_back(temp);
}
}
}
}
else//如果不是数字,说明遇到操作符了,需要对操作符进行一些操作
{
//如果是数字的话就直接输出成逆波兰表达式的一部分
reverse_polish.push_back(*it);
it++;
}
}
//结束循环之后栈里面必定还会剩下一个,这个时候全部按顺序出栈
while (!st.empty())
{
reverse_polish.push_back(st.top());
st.pop();
}
反逆波兰表达式,通过逆波兰表达式得到表达式的解
代码:
//先在我们已经得到了逆波兰表达式,现在我们要反逆波兰表达式,把式子的值算出来
stack<double> stack;//栈里面放的是string类的字符串
for (auto e : reverse_polish)
{
string temp(e);
if (!(temp == "+" || temp == "-" || temp == "*" || temp == "/"))
{
stack.push(atof(temp.c_str()));
}
else
{
double right = stack.top();
stack.pop();
double left = stack.top();
stack.pop();
switch (temp[0])
{
case '+':
stack.push(left + right);
break;
case '-':
stack.push(left - right);
break;
case '*':
stack.push(left * right);
break;
case '/':
stack.push(left / right);
break;
}
}
}
cout << "表达式的答案是:" << stack.top() << endl;
return 0;
}
注意:先出栈的东西是右操作数,因为栈是后进先出