后缀表达式
什么是后缀表达式
也许有的同学是第一次听到后缀表达式这个概念,那么什么是后缀表达式呢,结合名字你还会想到,有后缀表达式,那是不是也有前缀表达式呢?答案是肯定的,计算机的世界里一共有三种表达式,前缀、后缀、中缀。下面我们一起看一下这几个概念。
- 中缀表达式:中缀表达式是我们平时最常用的一种表达式,他的一般情形是:<操作数><操作符><操作数>,例如3 + 4
- 前缀表达式:前缀表达式顾名思义就是把操作符放到前面,例如+ 3 4
- 后缀表达式:那后缀表达式就明显了,肯定是操作符放后面,例如3 4 +
为什么要有后缀表达式
我们平时做计算是用的中缀表达式,这是一种便于我们人类理解阅读的表达方式。如果给定3 + 4 * (2 - 5)
。我们知道先算括号里面的减法在和4相乘最后算加法。但是计算机不行啊,他没有这么聪明啊,他是怎么算的呢。他一般就是用栈这种数据结构来算的。那么如果用前缀表达式让计算机算,他就需要两个栈,一个用来存操作数operandStack,一个用来存操作符operatorStack。具体步骤分两步:
- 如果是操作数,入栈到operandStack
- 如果是操作符,需要和栈顶的符号做一个比较,如果当前操作符优先级比栈顶的符号优先级高,就直接入栈到operatorStack。如果比栈顶的符号优先级低或者相同,就需要需要从符号栈里面出栈一个符号去做计算,然后将计算的结果在入栈到operandStack。
后缀表达式具体的计算过程
假设我们计算一个表达式:42+5+67=,他的计算顺序可以是将42的值存为A1,然后将A1和5相加,在将结果存入A1,然后在将67的值存为A2,最后将A1和A2相加,并将结果放入A1。那我们可以将这种操作顺序写成这样:4 25+6 7+。这个写法就是我们说的后缀(postfix)或逆波兰(reverse polish)写法。这种写法的在计算机中可以用一个栈来实现:遇见一个数,把他放到栈中,在遇到一个运算符时,该运算符作用于从该栈弹出的两个数上,在将结果放入到栈中,例如:6 5 2 3 + 8 * + 3 + *
1.将6 5 2 3放入栈中
2.下面读到一个+号,所以3和2从栈中弹出,把他们的和5压入栈中
3.接着8入栈
4.然后 读到* ,8和5弹出,40进栈
重复这个过程,最终得出正确结果。这样下来计算一个后缀表达式花费的时间是O(N)。并且不需要知道任何优先级规则,只用一个栈就可以。
中缀到后缀的转化
我们平时接触到的都是中缀表达式,因此如何将他转化成后缀表达式是有必要的,下面我们一起来讨论一下。
表达式:a+b*c+(d*e+f)*g
转化后是abc*+de*f+g*+
当读到一个数字的时候,放到输出中,操作符放入到一个栈中。具体的操作下面通过图说明:
1.首先,a被读入,放到输出中,然后 + 被读入,放到栈中,接下来,b读入放到输出中
2.接着读入,+ 没有 * 优先级高,+ 不输出, 进栈,c读入放到输出
3.接下来,+ 读入,+ 比 * 优先级底,所以将 * 从栈弹出到输出,弹出剩下的 +,该运算符和刚刚遇到的 + 优先级一样,然后将刚刚遇到的 + 压入栈中
4.接下里,最高优先级的 ( 读入,压入栈中,d 读入到输出中
5.继续,* 读入,压入栈中,e读入到输出
6.接续,+ 读入,比栈中的 * 优先级低,所以 * 出栈到输出,+ 压入栈中,f读入到输出
7.接下来,) 读入,因此将栈中的元素知道 ( 弹出
8.然后又读入 * ,压入栈中,g读入到输出
9.现在读入空,所以将栈中的符号全部弹出,知道变为空栈
这种转化只需要O(N)的时间并经过一趟输入后工作完成。
public class InfixToSuffix {
private static final Map<Character,Integer> PRIORITY_MAP = new HashMap<>();
private static final String OPERATOR = "*/+-()";
static{
PRIORITY_MAP.put('-', 1);
PRIORITY_MAP.put('+', 1);
PRIORITY_MAP.put('*', 2);
PRIORITY_MAP.put('/', 2);
PRIORITY_MAP.put('(', 0);
}
// 中缀表达式转换成后缀表达式
private String toSuffix(String infix){
List<String> suffix = new LinkedList<>();
Stack<Character> stack = new Stack<>();
// 用于记录字符长度 例如100*2,则记录的len为3 到时候截取字符串的前三位就是数字
int len = 0;
for(int i = 0; i < infix.length(); i++){
char ch = infix.charAt(i);
// 数字
if (Character.isDigit(ch) || ch == '.') {
len++;
} else if (OPERATOR.indexOf(ch) != -1) {
// 符号之前的可以截取下来做数字
if (len > 0) {
suffix.add(infix.substring(i-len, i));
len = 0;
}
// 将左括号放入栈中
if (ch == '(') {
stack.push(ch);
continue;
}
if (!stack.isEmpty()) {
int size = stack.size() - 1;
boolean flag = false;
// 若当前ch为右括号,则栈里元素从栈顶一直弹出,直到弹出到左括号
while(size >= 0 && ch == ')' && stack.peek() != '('){
suffix.add(String.valueOf(stack.pop()));
size--;
flag = true;
}
// 若取得不是()内的元素,并且当前栈顶元素的优先级>=对比元素 那就出栈插入队列
while(size >= 0 && !flag && PRIORITY_MAP.get(stack.peek()) >= PRIORITY_MAP.get(ch)){
suffix.add(String.valueOf(stack.pop()));
size--;
}
}
if (ch != ')') {
stack.push(ch);
}else {
stack.pop();
}
}
// 如果已经走到了中缀表达式的最后一位
if (i == infix.length() - 1) {
if (len > 0) {
suffix.add(infix.substring(i - len + 1, i + 1));
}
int size = stack.size() - 1;
// 一直将栈内 符号全部出栈并且加入队列中
while(size >= 0) {
suffix.add(String.valueOf(stack.pop()));
size--;
}
}
}
return suffix.toString().substring(1, suffix.toString().length()-1);
}
// 根据后缀表达式计算结果
private String calculate(String suffix){
String [] arr = suffix.split(",");
Stack<String> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
switch (arr[i].trim()) {
case "+":
double a = Double.parseDouble(stack.pop()) + Double.parseDouble(stack.pop());
stack.push(String.valueOf(a));
break;
case "-":
double b = Double.parseDouble(stack.pop()) - Double.parseDouble(stack.pop());
stack.push(String.valueOf(-b));
break;
case "*":
double c = Double.parseDouble(stack.pop()) * Double.parseDouble(stack.pop());
stack.push(String.valueOf(c));
break;
case "/":
double d = Double.parseDouble(stack.pop()) / Double.parseDouble(stack.pop());
stack.push(String.valueOf(d));
break;
default:
stack.push(arr[i].trim());
break;
}
}
return stack.size() == 1 ? stack.pop() : "运算失败" ;
}
/**
* 程序入口
*
* @param infix 中缀表达式
* @return 计算结果
*/
public String run(String infix) {
String suffix = toSuffix(infix);
return calculate(suffix);
}
public static void main(String[] args) {
InfixToSuffix test = new InfixToSuffix();
String result = test.run("20+5*(3-1)+9");
System.out.println(result);
}
}