2019独角兽企业重金招聘Python工程师标准>>>
在程序设计中,可能碰到需要对字符串数学表达式求值的问题,常用的方法是解析表达式,生成二叉树,然后进行计算。编译器就是使用这种方法来解析程序中的表达式的。这种方法实现起来有点难度,需要考虑运算符的优先级,括号的配对,堆栈的使用等等。我们正常情况下看到的数学表达式如果用二叉树遍历的话,恰好是中序遍历,故叫做中序表达式。除此之外,还有前序表达式,后序表达式。如:a+b+c(中序),++abc(前序),ab+c+(后序),如果表达式含有×,/,()等就更复杂了。
后缀表达式也称逆波兰表达式 因其使表达式求值变得轻松,所以被普遍使用。
逆波兰式:
在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间(如:1+1),所以这种表示法也称为中缀表示。波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,称为逆波兰记法,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。示例如下:
中缀表示 |
逆波兰式 |
a+b |
a,b,+ |
a+(b-c) |
a,b,c,-,+ |
a+(b-c)*d |
a,b,c,-,d,*,+ |
a+d*(b-c) |
a,d,b,c,-,*,+ |
a=1+3 |
a=1,3 + |
逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。它的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。
中缀表达式转换为逆波兰式:
将一个普通的中序表达式转换为逆波兰表达式的一般算法是:
1、首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
2、读入一个中缀表达式,为了方便起见,可在其最右端追加一个最低优先级运算符(如:#号)。(这样做的目的是,最后读入#号运算符时将运算符栈中所有运算符都输出)。
3、从左至右扫描该中缀表达式,如果当前字符是数字,则分析到该数字串的结束,并将该数字串直接输出。
4、如果不是数字,该字符则是运算符,此时需比较该运算符与运算符栈顶运算符的优先关系:
(1)、若该运算符优先级高于栈顶运算符优先级别(或栈为空),则直接将该运算符压入运算符栈中;
(2)、若该运算符优先级小于或等于此运算符栈顶的运算符,则弹出栈顶运算符并输出,重复比较、输出,直到栈为空或该运算符优先级高于栈顶运算符,然后将该运算符入栈。
5、重复上述操作(3)-(4)直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,输出结果便是中缀表达式转化为逆波兰表示的简单算术表达式。 中缀表达式(a+b)*c-(a+b)/e的逆波兰式是ab+c*ab+e/-。
逆波兰表达式是一种利用栈来进行运算的数学表达式。
以一个表达式 1 + 2 * 3 为例。
以顺序方式输入这个表达式,计算机首先需要解析这个表达式,然后递归的求值。
比如从左起进行顺序解析,得到一个符号树:
+
/ \
1 *
/ \
2 3
计算机会递归的从叶子开始求值,最后回到根,得出结果。
所以对于一个比较复杂的表达式,树可能会很深,而且递归的过程将会消耗大量的内存,由于有解析和运算两个过程,时间开销也不理想。
如果将上式改为逆波兰表达式: 3 2 * 1 +
那么就能实现 “在线处理”。在线处理必须是这类问题在计算机中最快的算法。
还是从左侧开始,扫描这个表达式。
扫描到第一项,为一个操作数,那么可以把这个数压栈,然后继续扫面。
- 3
第二项还是一个操作数,同样的压栈继续。
- 2
- 3
到第三项,得到一个双目运算符,这时候计算机就从操作数栈中弹出两个数作为运算符的两个参数,然后进行运算,并将结果再压回操作数栈。
- 6
接着第四项,一个操作数,压栈。第五项,又是一个双目运算符,那么弹出两个操作数进行运算,把结果压栈。
- 1
- 6
到此,这个表达式就扫描结束了,操作数栈中最后会剩下表达式的运算结果。
- 7
不需要两次操作,只要从头扫描到尾就能求出结果,也不需要递归,只需要一个很小的栈就可以。所以逆波兰表达式算法取得了时间复杂度和空间复杂度上的双重优势。
并且:
- 从左到右扫描,有运算符就运算,对于复杂的表达式,不容易出错;
- 栈可以自动的记录中间结果;
- 机器只需要维护一个操作数堆栈,栈中只有操作数和结果
所以在机器上实现起来非常的方便。早期,处理器性能比较弱的时候,使用这种方式就可以在性能不足的机器上实现任意复杂度的表达式运算。很棒吧。
至于你的问题,主要是在于“为啥我的汽车不能飞”,很简单,因为不支持。
使用一种软件的时候需要按照软件提供的功能来操作,编程语言也是一种软件系统,如果你想在这里面使用逆波兰表达式,那么需要软件提供对逆波兰表达式的支持。对于编程语言,就是语法级的支持。
编译器在编译时怎么实现的我没有研究过。但是对于常数值,在编译时就可以确定,对于变量,我想还是和平台相关吧。对于寄存器比较少的平台,有可能会把表达式的运算序列化成一个逆波兰表达式。只是有可能,没有研究过编译器的实现。
应用主要是任何基于栈的程序语言,计算机(相当大一部分工程计算机都是基于栈的)。
原题
Evaluate the value of an arithmetic expression in Reverse Polish Notation.
Valid operators are +, -, *, /. Each operand may be an integer or another expression.
Some examples:
["2", "1", "+", "3", "*"] -> ((2 + 1) * 3) -> 9
["4", "13", "5", "/", "+"] -> (4 + (13 / 5)) -> 6
题目大意
计算逆波半表达式的值,有效的运算符是:+、-、*、/,每个操作数要么是一个整数要么是另一个表达式
解题思路
使用栈进行操作
代码实现
算法实现类
import java.util.Stack;
public class Solution {
public int evalRPN(String[] tokens) {
// 参数校验
if (tokens == null || tokens.length < 1) {
throw new IllegalArgumentException();
}
int op1;
int op2;
// 操作数栈
Stack stack = new Stack<>();
for (String token: tokens) {
// 说明是运算符,要取栈顶两个元素进行运算
if ("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token)) {
// 取栈顶元素
op2 = stack.pop();
op1 = stack.pop();
// 进行运算
switch (token.charAt(0)) {
case '+':
op1 += op2;
break;
case '-':
op1 -= op2;
break;
case '*':
op1 *= op2;
break;
case '/':
op1 /= op2;
break;
}
// 结果入栈
stack.push(op1);
}
// 说明是操作数,入栈
else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
}
运算符优先级参考:
优先级分为栈内优先级isp(In stack priority)和栈外优先级icp(In coming priority)。除了括号以外,其他运算符进栈后优先级都升1,这样可以体现在中缀表达式中相同优先级的操作符自左向右计算的要求,让位于栈顶的操作符先退栈并输出。各运算符及符号优先级:
操作符 |
# |
^ |
*,/,% |
+,- |
( |
) |
isp |
0 |
7 |
5 |
3 |
1 |
8 |
icp |
0 |
6 |
4 |
2 |
8 |
1 |