示例:中缀表达式:1 + ( ( 2 + 3 )× 4) - 5 =》后缀表达式:1 2 3 + 4 * + 5 -
思路:
-
创建两个栈,运算符栈s1 和 数值栈s2。
-
从左到右扫描中缀表达式。
-
遇到数的话直接假如到数栈s2中
-
遇到运算符的话,分为以下几种情况:
1) 如果s1为空,或者s1的栈顶为左括号 ” (“ 就直接将运算符入栈
2)如果s1不为空,扫描到的运算符的优先级高于栈顶运算符的优先级的话,也直接入栈
3)如果s1不为空,扫描到的运算符的优先级小于或等于栈顶运算符的优先级的话,就将s1中的运算符弹出并压入到s2栈中,然后再转到(4.1)与s1中新的栈顶的运算符进行比较。
-
遇到括号的情况:
1)当遇到左括号时,直接入栈
2)当遇到右括号时,将s1栈中的符号依次弹出并压入到s2中,直到遇见左括号为止。然后将这一对括号丢弃
6.重复2-5的步骤,直到扫描到表达式的最右边(即表达式最后)
7.将s1中剩余的运算符依次弹出,并压入到s2中
8.依次弹出s2中的元素并输出,此时的结果的逆序就是我们要的后缀表达式
后缀表达式的计算:
思路:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
1)
从左至右扫描,将
3
和
4
压入堆栈;
2)
遇到
+
运算符,因此弹出
4
和
3
(
4
为栈顶元素,
3
为次顶元素),计算出
3+4
的值,得
7
,再将
7
入栈;
3)
将
5
入栈;
4)
接下来是
×
运算符,因此弹出
5
和
7
,计算出
7×5=35
,将
35
入栈;
5)
将
6
入栈;
最后是-运算符,计算出35-6的值,即29,由此得出最终结果。
代码:
package com.lzh.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* 逆波兰表达式的计算
*/
public class ReversePolandNotation {
public static void main(String[] args) {
//定义一个String变量,来接收后缀表达式
String suffixExpression = "3 4 + 5 * 6 - ";
//String suffixExpression = "3#4#+#5#x#6#-#";
String expression = "1+((2+3)*4)-5";//注意表达式(这是一个中缀表达式)
/**
* 思路:
* 1.创建一个ArrayList集合
* 2.把表达式中的每个字符串分割出来,放到集合中
* 3.然后把集合传到一个方法中,然后让这个方法进行表达式的计算即可
*/
List<String> list = getList(suffixExpression);
int res = calculate(list);
System.out.println(suffixExpression+"的结果是=>"+res);
// System.out.println(list);
//中缀表达式对应的集合
System.out.println("中缀表达式="+expression);
List<String> strlist = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的集合"+strlist);
//后缀表达式对应的集合
List<String> sufix = parseSuffixExpression(strlist);
System.out.println(expression+"对应的后缀表达式为:"+sufix);
//计算中缀表达式转为后缀表达式的结果
int r = calculate(sufix);
System.out.println(expression+"的结果为:"+r);
}
//接收一个字符串,把字符串分割后放入到集合中
public static List<String> getList(String suffixExpression){
String[] splist = suffixExpression.split(" ");
//String[] splist = suffixExpression.split("#"); // 用#或者空格分割都可以
List<String> list = new ArrayList<String>();
//把分割后的每个字符添加到集合中
for(String item : splist){
list.add(item);
}
return list;
}
//把集合中的元素遍历后,进行计算,然后入栈
/**
* 思路:
* 1.从左到右扫描字符串(这里直接遍历集合就可以了),遇到数字就放入到栈中
* 2.遇到符号后,从栈中pop出两个数,进行计算,然后把计算的结果放到栈中
* 3.如此反复,最后栈中的数据就是后缀表达式的结果
* 注意:这里只需要创建一个栈即可,我们只需要把数字放到栈中,遇到符号就直接计算了。
*/
public static int calculate(List<String> list){
//创建一个栈
Stack<String> stack = new Stack<String>();
//遍历集合
for (String item : list){
//这里使用正则表达式来取出数字
if (item.matches("\\d+")){ //匹配出是否是数字(多位数也能匹配)
stack.push(item);
}else {
//不是数字的话,就从栈中pop出两个数,进行计算
//定义变量来接收数据
int num1 = Integer.parseInt(stack.pop());//接收栈顶的数据
int num2 = Integer.parseInt(stack.pop());//接收次栈顶的数据
int res = 0;//用来接收结果
if (item.equals("+")){
res = num1 + num2;
}else if (item.equals("-")){
res = num2 - num1;
}else if (item.equals("*")){
res = num1 * num2;
}else if (item.equals("/")){
res = num2 / num1;
}else{
throw new RuntimeException("运算符有问题,无法进行计算");
}
//计算之后把结果入栈,因为栈是String类型的,这里我们把这个结果拼接一个字符串,就实现了int快速转换成String类型了
stack.push(""+res);
}
}
//最后栈中的数据就是表达式的结果,我们把这个结果返回就行
return Integer.parseInt(stack.pop());
}
//中缀表达式转换成后缀表达式的方法
/**
* 思路:
* 1.先把给定的字符串表达式扫描出来放到list集合中,这样方便我们后续的操作
* 2.判断每个字符是运算符还是数字:
* 2.1 如果是运算符的话就直接放入到list集合中
* 2.2 如果是数字的话 我们要判断一下这个数是否是一个多位数,如果是多位数,我们把这个数字拼接上之后再放入到list集合中
* toInfixExpressionList:方法名说明:中缀表达式对应的list集合
*/
public static List<String> toInfixExpressionList(String expression){
//先创建一个list集合
List<String> list = new ArrayList<String>();
//定义相关变量
String str ; //用于多位数的拼接
int i = 0; //一个指针,用来扫描中缀表达式的字符串
char c; // 用来接收扫描到的每一个字符
//这里用一个do...while()循环
do {
//如果是非数字,直接添加到list集合中
if((c = expression.charAt(i) ) < 48 || (c = expression.charAt(i)) > 57){
list.add("" + c);
i++;//后移
}else{
//str每次拼接都重置成""
str = "";
//循环拼接多位数,ASCII表中 '0'[48]~'9'[57] 大于等于48~小于等于57是数
while(i < expression.length() && (c=expression.charAt(i))>=48 && (c = expression.charAt(i))<=57){
str += c;
i++;//i后移,往后判断是否还有数字
}
//拼接完多位数后,把数字放到集合中
list.add(str);
}
}while(i < expression.length());
return list;
}
//中缀表达式转后缀表达式的方法
public static List<String> parseSuffixExpression(List<String> strList){
//先创建栈,用来存放符号和数值
Stack<String> s1 = new Stack<String>();//用来存放符号
//Stack<String> s2 = new Stack<String>();//用来存放数字
//因为s2这个栈只有入栈操作 没有出栈操作,到最后还要逆序打印,这里我们可以用一个list集合代替
List<String> s2 = new ArrayList<String>();
//从左到右扫描中缀表达式 (即遍历我们传过来的list集合)
for (String item : strList){
//如果是一个数,直接加入到s2中
if (item.matches("\\d+")){//用正则表达式来判断是否是一个数(同时多位数也能判断)
s2.add(item);
}else if (item.equals("(")){//当遇到左括号时,直接入栈
s1.push(item);
}else if (item.equals(")")){//当遇到右括号时,将s1栈中的符号依次弹出并压入到s2中,直到遇见左括号为止。然后将这一对括号丢弃
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
}else{
/**
* 如果s1不为空,扫描到的运算符的优先级小于或等于栈顶运算符的优先级的话,就将s1中的运算符弹出并压入到s2栈中,然后再转到
* (4.1)与s1中新的栈顶的运算符进行比较。
* 存在的问题:但是我们还缺少一个比较运算符优先级的方法
*/
while(s1.size()!=0 && Operation(item) <= Operation(s1.peek())){
s2.add(s1.pop());
}
//然后把当前扫描到的运算符压入到s1中
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出,并压入到s2中
while(s1.size()!=0){
s2.add(s1.pop());
}
return s2;
}
//比较运算符优先级的方法
public static int Operation(String str){
int result = 0;//用于返回优先级
switch (str){
case "+" :
result = 1;
break;
case "-" :
result = 1;
break;
case "*" :
result = 2;
break;
case "/" :
result = 2;
break;
default:
result = 0; //如果不是这四个运算符 就默认返回0(比如是括号的时候,就返回0,括号在栈外优先级最高,在栈内优先级最低)
break;
}
return result;
}
}