中缀表达式是最为常见、也是我们平时使用的表达式。例如,4+3*2 就是中缀表达式。可以理解为运算符放在了两个需要运算的数中间,而后缀表达式,可以理解为:把运算符放在了需要运算的数后面。后缀表达式也称为逆波兰表达式。
【注意】后缀表达式没有括号
计算一个后缀表达式
在计算之前,我认为有必要介绍一个将中缀表达式转换为后缀表达式的方法,但这个方法无法使用编程描述起来,需要依靠人去观察 。能够使用编程语言实现,而且也是考研的方法,我放在了本文的下半部分。
例一
中缀表达式:4 + 3 * 2
优先级越高的越先处理,3 * 2 👉3 2 *
3 2 *当成一个整体,4
和3 2 *
看作两个需要运算的数,4 + 3 2 * 👉4 3 2*+
例二
中缀表达式:(4 + (13 / 5))
按照优先级来慢慢处理,13/5 👉13 5 /
4
和13 5 /
看作两个需要运算的数,+
放在两个需要数的后面,化为逆波兰表达式为4 13 5 / +
例三
中缀表达式:6 / ((9 + 3) * -11)
优先级越高的优先处理,9 + 3👉9 3 +
下一个处理 (9 + 3) * -11,9 3 +
和-11
看作两个需要运算的数,化为9 3 + -11 *
将6
和9 3 + -11 *
看作两个需要运算的数,把/
放在两个数的后面
最终化为6 9 3 + -11 * /
既然我们能够将一个中缀表达式转化为后缀表达式,那当然也能够将后缀转化为中缀表达式。
按照中缀表达式转化为后缀表达式的方法,运算符放在两个运算数的后面。所以给出一个逆波兰表达式,从左至右开始,一个运算符和它前面的两个运算数,构成一个子表达式或者全部表达式。
以6 9 3 + -11 * /
为例,将它化成一个中缀表达式
从左至右,找到第一个运算符,为
+
。
那么它前面的两个数9
、3
,和+
构成一个子表达式,即9 + 3
构成一个子表达式的那一堆,看作一个数、一个整体。
继续往后面找运算符,为*
前面的两个数为9 3 +
和-11
, 即9 + 3
、-11
和*
构成一个子表达式(9 + 3) * -11
。
继续往后面找运算符,为/
前面的两个数为6
和(9 + 3) * -11
,所以化为中缀表达式为6 / ((9 + 3) * -11)
有一道经典的题目,根据 逆波兰表示法,求一个后缀表达式的计算结果。还是以6 9 3 + -11 * /
为例,点开解题思路。
有两种我能想到的思路,第一种是按照上面的方法,将它完完整整地化为中缀表达式后,进行计算;第二种思路,是在转化为中缀表达式的同时计算结果,过程如下:
【1】从左至右,找到第一个运算符,为
+
。
那么它前面的两个数9
、3
,和+
构成一个子表达式,9 + 3
, 即为12。
此时这个后缀表达式形式:6 12 -11 * /
。
【2】继续往后面找运算符,为*
。
前面的两个数为12
和-11
, 即12
、-11
和*
构成一个子表达式12 * -11
, 即-144。
此时这个后缀表达式形式:6 -144 /
【3】继续往后面找运算符,为/
前面的两个数为6
和-144
,表达式变成6/-144
综合第二种思想,实际实现的时候,借助栈来实现,简直再合适不过了。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
//前提:给的逆波兰表达式总是合法的
stack<int> st;
int LeftNum = 0, RightNum = 1, num = 0;
for(string e : tokens)
{
//如果e是运算符,取出栈顶的两个数据出来运算
if(e == "+" || e == "-" || e == "*" || e == "/")
{
//注意哪一个作为左运算数、哪一个作为右运算数
RightNum = st.top();
st.pop();
LeftNum = st.top();
st.pop();
//运算,运算的结果压入栈
if(e == "+")
num = LeftNum + RightNum;
else if(e == "-")
num = LeftNum - RightNum;
else if(e == "*")
num = LeftNum * RightNum;
else
num = LeftNum / RightNum;
//运算结果压入栈里面
st.push(num);
}
else{
//e属于运算数,入栈
st.push(stoi(e));
}
}
//栈里面只剩下一个数据,这就是最终结果
return st.top();
}
};
中缀表达式转为后缀表达式
需要借助栈,同时牢记下面几条规则,这几条规则是我简单总结出来的。
- 在比较时,保持栈顶优先级最高, 并且以当前优先级作为基础。
- 普通运算符(除了
“()”
),进栈后优先级等比例变高。等比例也就是说,即使+
在栈里面,优先级也不会比栈外的*、/
高。 - 左括号
(
, 在栈外优先级最高,在栈内优先级最低;右括号)
, 在栈外优先级最低。 - 遇到
)
直接进栈, 栈内如果出现一对()
, 将()
以及()
里的全部内容出栈。实际上)
不用进栈, 反正也会直接退出来。 - 第一个运算符直接入栈。
- 后缀表达式没有括号
使用一个例子来讲解它的过程
例一:将中缀表达式a+b-a*((c+d)/e-f)+g
转换为后缀表达式。
从左到右依次扫描,如果是运算数,直接输出。如果是运算符, 则按照上述规则操作。
🍬扫描到运算数a
, 直接输出。
🍭扫描到运算符+
, 是第一个运算符,直接进栈。
🍡扫描到运算数b
, 直接输出。
🍮扫描到运算符-
。-
此时在栈外, 要和栈顶元素比较。由于普通运算符进栈后优先级变高,所以当前优先级+
比-
高。在比较时,要求栈顶元素优先级最高, 并且以当前优先级作为基础。为了保持栈顶元素优先级最高, 需要把+
先退栈,然后让 -
进来。
🍯扫描到运算数a
, 直接输出。
🍼扫描到运算符*
。开始和栈顶元素比较。当前*
在栈外, -
在栈内。当前优先级*
高于-
, 直接进栈没有任何问题。
🥛扫描到运算符(
。开始比较, 当前(
在栈外,*
为栈顶元素。由于(
在栈外优先级最高, 所以当前优先级(
比栈顶元素*
高。直接进栈, 没有任何问题。
☕扫描到运算符(
。开始和栈顶元素比较, 当前(
在栈外, 栈顶元素也为(
。但我们要记住, (
在栈内优先级最低, 而在栈外优先级最高, 所以当前优先级栈外(
优先级高于栈内(
。直接进栈,没有任何问题。
🍵扫描到运算数c
, 直接输出。
🍶扫描到运算符+
。开始比较, 当前+
在栈外, 栈顶元素为(
。因为(
在栈内优先级最低,所以当前优先级+
高于(
。 直接进栈,没有任何问题。
🍾扫描到运算数d
, 直接输出。
🍷扫描到运算符)
。)
并不需要进入栈内, 假设让它直接进栈,那么栈内就构成了一对()
, 将()
以及()
内的元素全部依次出栈, 并且输出。但是后缀表达式没有括号, 所以括号不要输出。
🍸扫描到运算符/
。开始比较, 当前/
在栈外,栈顶元素为(
。因为(
进栈后优先级最低, 所以当前优先级/
高于(
。直接进栈,没有任何问题。
🍹扫描到e
,直接输出
🍍扫描到-
。开始比较 ,当前栈顶元素为/
, -
在栈外。当前优先级/
高于-
。为了保持栈顶优先级最高, -
不能直接进栈, 需要先把/
退栈,退栈后栈顶元素为(
。-
在栈外, (
在栈内优先级最低, 当前优先级(
低于-
, 此时直接进栈, 没有任何问题。
🍎扫描到运算数f
, 直接输出。
🍏扫描到运算符)
。)
不需要真正进栈, 假设让)
直接进栈, 栈内就存在一对()
,将()
以及()
内的全部内容依次退栈输出。要注意,后缀表达式没有括号。
🍐扫描到运算符+
。开始比较, 当前+
在栈外, 栈顶元素为*
。普通运算符进栈后优先级会变高, *
在栈外时就比它高, 那么进栈后优先级仍然比它高。所以+
不能直接进栈, 需要先把*
退栈输出,此时栈顶元素为-
。普通运算符进栈后优先级会变高, 当前优先级-
高于栈外的+
, 所以-
也需要退栈并输出。
🍑扫描到运算数g
, 直接输出
🍒全部扫描完成, 将栈内还剩余的元素依次退栈并输出。
所以a+b-a*((c+d)/e-f)+g
转换为后缀表达式就是ab+acd+e/f-*-g+
。