背景
在写这篇博客之前让我想到了一年多以前去小米面试,就是这道面试题让我挂了,后来听同事说这不就是数据结构里的例题吗,后来一看还真是,不过书里不太完整,看了几篇博客,学习了下解决方法,但是写的还是不太完美,不知道读者你有没有更优雅的代码,废话不多说开始学习吧。
介绍
栈大家都知道,一种先进后出的数据结构,可以自己使用线性表实现(数组,链表)。这篇写的是栈的应用,一般栈的应用有两个,一个是表达式求值问题,另一个就是迷宫问题,后一个我们后面再说,先说表达式求值。
1.什么是表达式求值
即给你一个字符串如”12*(3+(2+3)+4)-6+8/2”,让你求出字符串所代表的表达式的值,js里就有一个eval()函数,可以实现这个功能,当然我们要自己实现啦。
2.使用哪种方式解决这题
表达式求值问题,其实可以有好多种做法,这里使用的将表达式转换为后缀表达式,然后再将求后缀表达求值
3.关于后缀表达式
后缀表达式也叫逆波兰表达式,下面引用维基百科的解释来说明后缀表达式:
“逆波兰记法中,操作符置于操作数的后面。例如表达“三加四”时,写作“3 4 +”,而不是“3 + 4”。如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 - 4 + 5”在逆波兰记法中写作“3 4 - 5 +”:先3减去4,再加上5。使用逆波兰记法的一个好处是不需要使用括号。例如中缀记法中“3 - 4 * 5”与“(3 - 4)5”不相同,但后缀记法中前者写做“3 4 5 -”,无歧义地表示“3 (4 5 ) -”;后者写做“3 4 - 5 ”。”
中缀表达式也就是我们常用的表达式虽然便于人的理解,但是由于有括号和符号优先级的问题,计算机是不好计算的,而引入后缀表达式即可去掉括号并且符号的出现先后顺序即使用顺序,那么问题就变成可如何讲中缀表达式转换为后缀表达式,后缀表达式求值就简单了,这里面都用到了栈
思路
1.中缀表达转成后缀表达式
其实中缀转后缀只要理解一个规则就好了,这里是网上一个人的总结:
从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,并将当前符号进栈,一直到最终输出后缀表达式为止
具体做法:
(1)创建一个存储后缀表达的地方A
(2)创建一个存放运算符的栈S
(3)循环遍历表达表达式,只要是数字,就往A中添加;
如果左括号就进运算符栈S,如果是右括号,就出栈,出栈的同时就将出栈的运算符添加到A,一直到第一个与之匹配的左括号,将左括号也出栈,但是不添加到A;
如果是运算符,就判断它与运算符栈S的栈顶的操作符的优先级,如果栈顶元素是左括号则,直接添加,如果小于等于栈顶运算符优先级就将栈顶元素出栈并添加到A,如果大于栈顶元素优先级则直接入栈;
以此类推,循环遍历完成后将所有操作符出栈添加到A
2.后缀表达式求值
后缀表达式求值比较简单,也用到了栈,具体是循环遍历后缀表达式,如果是数字则入栈,如果是运算符就将栈顶元素出栈,作为num2,然后再出栈一个元素num1,计算num1 运算符 num2的结果,然后再将结果入栈,以此类推,最后循环遍历完最后栈中会只剩一个元素,这个即为后缀表达式的结果
下面就上代码了
代码
package com.liao;
import java.util.*;
/**
* 使用LinkedList实现实现栈(LinkedList底层使用链表实现)
*/
class Stack<E> {
private LinkedList<E> linkedList = null;
public Stack() {
this.linkedList = new LinkedList<>();
}
/**
* 入栈
*
* @param e
*/
public void push(E e) {
this.linkedList.push(e);
}
/**
* 出栈
*/
public E pop() {
return this.linkedList.pop();
}
/**
* 返回栈顶元素
*
* @return
*/
public E get() throws NoSuchElementException {
// 当没有元素时抛出异常
return this.linkedList.getFirst();
}
/**
* 返回栈的大小
*
* @return
*/
public int size() {
return this.linkedList.size();
}
}
/**
* 表达式求值
*/
public class Expression {
// 设置优先级 加减的优先级比乘除的优先级低
private static Map<String, Integer> priorityMap = new HashMap<>();
static {
priorityMap.put("+", 0);
priorityMap.put("-", 0);
priorityMap.put("*", 1);
priorityMap.put("/", 1);
}
/**
* 表达式中缀转后缀
*
* @param inOrderStr
* @return
*/
public List<String> inOrderToPostOrder(String inOrderStr) {
List<String> postOrderList = new ArrayList<>();
// 规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,
// 则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,
// 并将当前符号进栈,一直到最终输出后缀表达式为止
// 用来存储操作符
Stack<String> operatorStack = new Stack<>();
char[] inOrderCharArr = inOrderStr.toCharArray();
StringBuilder sb = new StringBuilder();
int length = inOrderCharArr.length;
for (int x = 0; x < length; x++) {
char ch = inOrderCharArr[x];
String chStr = Character.toString(ch);
// 当字符是'0' - '9'时
if (ch >= 48 && ch <= 57) {
// 如果是数字则使用StringBuilder保存
sb.append(ch);
// 当是最后一个时,直接添加
if (x == length - 1) {
postOrderList.add(sb.toString());
}
} else {
// 如果不是则将之前的数字放入List
String numberStr = sb.toString();
// 排除第一是括号的情况
if (numberStr.length() > 0) {
postOrderList.add(numberStr);
sb = new StringBuilder();
}
try {
// 如果是运算符或者括号
if (ch == '(') {
// 如果是左括号,则直接入操作符栈
operatorStack.push(chStr);
} else if (ch == ')') {
// 如果是右括号,则出栈一直到第一个匹配的"("
while (true) {
String operatorStr = operatorStack.pop();
if ("(".equals(operatorStr)) break;
postOrderList.add(operatorStr);
}
} else if (priorityMap.keySet().contains(chStr)) {
String operatorStr = operatorStack.get();
// 如果碰到栈顶元素为'(',则直接进栈
if ("(".equals(operatorStr)) {
operatorStack.push(chStr);
} else {
if (priorityMap.get(chStr) > priorityMap.get(operatorStr)) {
// 当前的优先级大于栈顶的优先级,则入栈
operatorStack.push(chStr);
} else {
// 当前的优先级小于栈顶的优先级,则前一个出栈,当前这个入栈
operatorStack.pop();
postOrderList.add(operatorStr);
operatorStack.push(chStr);
}
}
}
} catch (Exception e) {
// 当操作符栈为空时直接添加
operatorStack.push(chStr);
}
}
}
// 将剩下的操作符添加到后缀表达式
while (true) {
try {
String operator = operatorStack.pop();
postOrderList.add(operator);
} catch (Exception e) {
break;
}
}
return postOrderList;
}
/**
* 后缀表达式求值
*
* @return
*/
public int postOrderEval(List<String> postOrderList) {
Stack<String> stack = new Stack<>();
for (String postOrder : postOrderList) {
if (priorityMap.keySet().contains(postOrder)) {
int num2 = Integer.valueOf(stack.pop());
int num1 = Integer.valueOf(stack.pop());
int res = eval(num1, num2, postOrder);
stack.push(String.valueOf(res));
} else {
stack.push(postOrder);
}
}
return Integer.valueOf(stack.pop());
}
/**
* 求值
*
* @param num1
* @param num2
* @param oprator
* @return
*/
public int eval(int num1, int num2, String oprator) {
switch (oprator) {
case "+":
return num1 + num2;
case "-":
return num1 - num2;
case "*":
return num1 * num2;
case "/":
return num1 / num2;
default:
throw new RuntimeException("不支持的操作符");
}
}
public static void main(String[] args) {
String inOrderStr = "12*(3+(2+3)+4)-6+8/2";
List postOrderList = new Expression().inOrderToPostOrder(inOrderStr);
System.out.println(postOrderList);
System.out.println(new Expression().postOrderEval(postOrderList));
}
}