先简单的说一下栈,Stack栈数据结构遵从先进后出的原则,有着十分广泛的使用,如:
1.java中子程序的调用:
在执行子程序时,会将下个指令的地址存入栈中,子程序执行完毕后再取出地址,回到原来的程序。
2.处理递归调用:
和处理子程序时类似,不过除了下个指令的地址外,也会将局部变量表,操作数栈,动态连接以及方法返回地址一并压入栈中。
3.表达式的转换与求值(如:中缀表达式转后缀表达式并运算)
4.二叉树的遍历
5.图形的深度优先(depth-first)搜索法
必要知识:
关于前缀表达式,中缀表达式和后缀表达式的初步了解
中缀表达式是日常生活中最常见的,也是最容易被人理解的表达式,如:((1+2)*3)/4+5
就是一个中缀表达式,符号在数字的中间
而这种表达式对于计算机来说是不容易理解和处理的,计算器的实现逻辑就是基于后缀表达式来实现计算。
将中缀表达式转为后缀表达式的方法有很多种,这里说一下最简单的:
先看表达式中计算优先级最高的,如((1+2)*3)/4+5 中
优先级最高的是(1+2),将其看成一个整体,去除括号,数字放左边,符号右移得到 1 2 +;
然后将其看成一个整体,继续与*3进行处理
(1 2 + *3)=> 1 2 + 3 *
继续按照这个步骤处理
1 2 + 3 * /4 => 1 2 + 3 * 4 /
最后一步
1 2 + 3 * 4 / +5 => 1 2 + 3 * 4 / 5 +
即可得到后缀表达式,也叫逆波兰表达式。
逆波兰表达式的计算规则为,从左至右,拿出两个数字,遇到符号则计算,计算完毕后,用这个新数字去取下一个数字,随后继续计算,直至表达式的最右边。
按照这个规则,1 2 + 3 * 4 / 5 +计算的过程为:
1+2 = 3;
3*3=9;
9/4=2;(java中整形计算自动去除小数)
2+5=7;
模拟计算逆波兰表达式
根据逆波兰表达式的计算逻辑,先来模拟计算逆波兰表达式
//处理逆波兰表达式,通过集合存储
public static List<String> getListString(String suffixExpression){
String[] result = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String ele : result) {
list.add(ele);
}
return list;
}
这里为了方便计算,将逆波兰表达式通过作为参数传入getListString方法,将其转换为List集合
(对表达式的格式扫描在后面处理,此方法只考虑表达式规范的情况,不校验是否合法)
//传入list集合计算结果
public static int calculate(List<String> list){
//逆波兰表达式计算思路,从左往右计算,如果是数字则入栈,遇到符号则取出数字并计算
//创建栈
Stack<String> stack = new Stack<>();
int num1,num2,res;
for (String item : list) {
//如果是数字则入栈,通过正则表达式判断
if(item.matches("\\d+")){
stack.push(item);
}else if(item.equals("+")){
num2 = Integer.parseInt(stack.pop());
num1 = Integer.parseInt(stack.pop());
res = num1+num2;
stack.push(""+res);
}else if(item.equals("-")){
num2 = Integer.parseInt(stack.pop());
num1 = Integer.parseInt(stack.pop());
res = num1-num2;
stack.push(""+res);
}else if(item.equals("*")){
num2 = Integer.parseInt(stack.pop());
num1 = Integer.parseInt(stack.pop());
res = num1*num2;
stack.push(""+res);
}else if(item.equals("/")){
num2 = Integer.parseInt(stack.pop());
num1 = Integer.parseInt(stack.pop());
res = num1/num2;
stack.push(""+res);
}else{
throw new RuntimeException("未知的运算符");
}
}
return Integer.parseInt(stack.pop());
}
计算的方法如上,核心逻辑需要通过栈处理,num1,num2接收计算的数字,res接收计算结果;
public static void main(String[] args) {
String suffixExpression = "30 4 + 5 * 6 -";
//思路,先将给定的逆波兰表达式处理分割后存入List集合
List<String> listString = getListString(suffixExpression);
// System.out.println(listString);
int calculate = calculate(listString);
System.out.println(calculate);
}
在主方法中给定逆波兰表达式: "30 4 + 5 * 6 -"
计算结果为:164
将表达式改为:"4 5 * 8 - 60 + 8 2 / +"
计算结果为:76
到这里,核心的计算逻辑ok,那么接下来的问题便是如何讲中缀表达式转换为逆波兰表达式
操作流程是:
1.初始化两个栈,运算符号栈s1和中间结果栈s2 2.从左到右扫描中缀表达式 3.遇到操作数时,压入s2 4.遇到运算符时,比较其与 s1栈顶运算符的优先级 1.如果s1为空,或栈顶运算符号为左括号“(”则直接入s1 2.如果运算符优先级比s1栈顶运算符优先级高则直接入栈 3.否则将s1栈顶运算符弹出,并存入s2,再与s1栈顶运算符比较 5.遇到括号时,如果是左括号,直接压入s1,如果是右括号则依次弹出s1栈中数据,并弹入s2,直到遇见左括号,并舍弃这对括号 6.重复上述步骤,直到表达式的最后边 7.将s1中剩余的运算符依次弹出并压入s2 8.依次弹出s2并输出,结果的逆序即为后缀表达式
在执行之前得先补上对表达式的校验,判断表达式是否合法:
/**
* 将中缀表达式处理后存入list集合
* @param infixExpression 传入的中缀表达式
* @return
*/
public static List<String> toInfixExpressionList(String infixExpression){
String newInfix = infixExpression.replaceAll("\\s+","");
List<String> list = new ArrayList<>();
//分情况判断,如果为符号则直接加入集合,如果为数字则判断下一位是否还是数字
char c;
String res;
for (int i = 0; i < newInfix.length(); i++) {
String item = String.valueOf(newInfix.charAt(i));
if(item.matches("[+\\-*/()]")){
list.add(item);
}else if(item.matches("\\d+")){
if(i == newInfix.length()-1){
list.add(item);
}else{
res = "";
while(String.valueOf(c=newInfix.charAt(i)).matches("\\d+")){
res+=c;
i++;
}
list.add(res);
i--;
}
}
}
return list;
}
判断是否为数字时,如果为数字,则判断当前位置是否已经达到了表达式的末尾,如果是则直接加入集合,否则从当前位置开始while循环,直到下一位不是数字,中间使用字符串拼接,循环结束后,将拼接结果加入集合。(由于循环结束前,会再判断一次当前位置是否是数字,如果是符号的话就不执行了,而此时的i实际上是符号,如果不进行i--则会漏掉这个符号)
public static int priority(String s){
switch(s){
case "*":
return 1;
case "/":
return 1;
case "+":
return 0;
case "-":
return 0;
default:
return -1;
}
}
新增一个priority方法判断运算符的优先级
/**
* 将中缀表达式转换成后缀表达式
* @param list 传入的中缀表达式
* @return
*/
public static List<String> infixToSuffixExpression(List<String> list){
List<String> resultList = new ArrayList<>();
//1.初始化两个栈,运算符号栈s1和中间结果栈s2
Stack<String>operStack = new Stack<>();
Stack<String>tempStack = new Stack<>();
//2.从左到右扫描中缀表达式
//3.遇到操作数时,压入s2
//4.遇到运算符时,比较其与 s1栈顶运算符的优先级
//1.如果s1为空,或栈顶运算符号为左括号“(”则直接入s1
//2.如果运算符优先级比s1栈顶运算符优先级高则直接入栈
//3.否则将s1栈顶运算符弹出,并存入s2,再与s1栈顶运算符比较
//5.遇到括号时,如果是左括号,直接压入s1,如果是右括号则依次弹出s1栈中数据,并弹入s2,直到遇见左括号,并舍弃这对括号
//6.重复上述步骤,直到表达式的最后边
for (String s : list) {
if(s.matches("\\d+")){
tempStack.push(s);
}else if(s.matches("[+\\-*/()]")){
boolean flag = true;
while(flag){
if(operStack.isEmpty()||operStack.peek().equals("(")){
operStack.push(s);
flag = false;
}else if(s.equals(")")){
while(true){
String next = operStack.pop();
if(next.equals("(")){
flag = false;
break;
}
tempStack.push(next);
}
}else if(priority(s)>priority(operStack.peek())){
operStack.push(s);
flag = false;
}else if(s.equals("(")){
operStack.push(s);
flag = false;
}else{
tempStack.push(operStack.pop());
flag =true;
}
}
}
}
//7.将s1中剩余的运算符依次弹出并压入s2
while(operStack.size()!=0){
tempStack.push(operStack.pop());
}
//8.依次弹出s2并输出,结果的逆序即为后缀表达式
while(tempStack.size()!=0){
resultList.add(tempStack.pop());
}
Collections.reverse(resultList);
return resultList;
}
转换的核心逻辑
public static void main(String[] args) {
//将传入的中缀表达式转换为集合方便计算
List<String> InfixExpression = toInfixExpressionList("7+((2+3)*6)-(5+(5*2-1))");
//将中缀表达式转为后缀表达式
List<String> suffixExpression = infixToSuffixExpression(InfixExpression);
//计算表达式
int calculate = calculate(suffixExpression);
System.out.println(calculate);
}
测试计算中缀表达式:"7+((2+3)*6)-(5+(5*2-1))"
计算结果为:23
功能实现完毕!