对于一个字符串类型的表达式,实现基础的加减乘除运算
String expression = "8/2+2*5-4";
使用栈来实现这个表达式的解析和运算
实现思路:
1,通过一个index来遍历我们的表达式
2,如果发现是一个数字,判断下一位是不是运算符,如果是,把数字入数栈。如果不是,继续扫描,拼接数字
3,如果发现是一个运算符,判断符号栈是否为空,如果为空,直接入符号栈。如果不为空,就跟符号栈中栈顶的符号进行比较,如果优先级小于等于栈顶的符号,就需要从数栈中pop出两个数,从符号栈中pop出一个符号进行运算(注意运算时候的顺序,在进行减和除的时候,后面出来的数在前),将得到的结果入数栈,将当前扫描到的符号入符号栈。如果当前符号的优先级大于符号栈中栈顶的符号,就直接把当前符号入符号栈。
4,当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,进行运算。
5,最后数栈中只有一个数,就是表达式的结果。
代码实现:
public class StackCompute {
@Test
public void main(){
//表达式
String expression = "8/2+2*5-4";
//创建两个栈,一个数栈,一个符号栈
Stack<Integer> numStack = new Stack<>();
Stack<Character> operStack = new Stack<>();
//定义需要的变量
int index = 0;//用于扫描表达式
int num1 = 0;
int num2 = 0;
int oper = 0;//符号
int result = 0;//运算结果
char ch = ' ';//将每次扫描到char保存到ch
String keepNum = "";//拼接数
//开始while循环扫描表达式
while(true){
//依次得到表达式中的每一个字符
ch = expression.substring(index,index+1).charAt(0);
//判断ch是什么,然后做相应的处理
if(isOper(ch)){//如果是一个运算符
//判断当前符号栈是否为空
if(!operStack.isEmpty()){
/*
如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数
在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前扫描到的操作符入符号栈
*/
if (priority(ch) <= priority(operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = cal(num1,num2,oper);
//把运算结果入数栈
numStack.push(result);
//当前符号入符号栈
operStack.push(ch);
}else{
//如果当前符号的优先级大于栈中符号的优先级,就直接入符号栈
operStack.push(ch);
}
}else{
//如果为空,直接入栈
operStack.push(ch);
}
}else{
//如果是数,入数栈
//处理多位数
keepNum += ch;
//如果是表达式的最后一位。直接入数栈
if(index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else{
//如果不是最后一位,则判断后一位是不是字符,如果是字符,则入栈,反之,则继续扫描
if(isOper(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
keepNum = "";//清空keepNum
}
}
}
index++;
if(index >= expression.length()){
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
while(true){
//如果符号栈为空,表示已经计算到最后的结果,数栈中此时应该只有一个数字,就是结果
if(operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = cal(num1,num2,oper);
//把运算结果入数栈
numStack.push(result);
}
System.out.println(numStack.pop());
}
//判断是否是一个运算符,目前仅限于加减乘除
boolean isOper(int ch){
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
//判断优先级
int priority(int ch){
if(ch == '*' || ch == '/'){
return 1;
}else{
return 0;
}
}
//计算方法
int cal(int num1,int num2,int oper){
int result = 0;
switch (oper){
case '+': result = num1 + num2;break;
case '-': result = num2 - num1;break;//注意顺序,后出来的数做被减数
case '*': result = num1 * num2;break;
case '/': result = num2 / num1;break;//注意顺序,后出来的数做被除数
}
return result;
}
}
在计算机中,前缀表达式(波兰表达式)或者后缀表达式(逆波兰表达式)比中缀表达式更好解析。
前缀表达式
后缀表达式
后缀表达式的求值:
public class PolandNotation {
public static void main(String[] args) {
//定义一个逆波兰表达式
//5*7-8+3+9/3 => 5 7 * 8 - 3 + 9 3 / +
//为了方便,我们吧逆波兰表达式的数字和符号之间使用空格隔开
String suffixExpression = "5 7 * 8 - 3 + 9 3 / +";
/**
* 思路
* 1,先将表达式放入一个ArrayList中
* 2,遍历ArrayList配合栈完成计算
*/
List<String> list = getListString(suffixExpression);
System.out.println(calculate(list));
}
//将表达式转换成集合
public static List<String> getListString(String suffixExpression) {
List<String> list = new ArrayList<String>();
String[] split = suffixExpression.split(" ");
for (String ele : split) {
list.add(ele);
}
return list;
}
/**
* 完成对逆波兰表达式的计算
* 思路分析:
* 1,从左至右扫描,将3和4压入堆栈;
* 2,遇到+运算符,因此弹出4和3 ( 4为栈顶元素, 3为次项元素),计算出3+4的值,得7,再将7入栈;
* 3,将5入栈;
* 4,接下来是*运算符,因此弹出5和7,计算出7*5=35,将35入栈;
* 5,将6入栈;
* 6,最后是-运算符,计算出35-6的值,即29,由此得出最终结果;
*
* @param ls
* @return
*/
public static int calculate(List<String> ls) {
//创建一个栈
Stack<String> stack = new Stack<>();
for (String item : ls) {
if (item.matches("\\d+")) {//如果是数字
stack.push(item);//入栈
} else {
//pop出两个数进行运算后再入栈
//因为需要用后面弹出的数做被减数或被除数,所以为了方便,这里第一个弹出的数命名为num2
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int result = 0;
if (item.equals("+")) {
result = num1 + num2;
} else if (item.equals("-")) {
result = num1 - num2;
} else if (item.equals("/")) {
result = num1 / num2;
} else if (item.equals("*")) {
result = num1 * num2;
}else{
throw new RuntimeException("运算符有误");
}
//把结果入栈
stack.push(result+"");
}
}
//最后留在栈中的数就是运算结果
return Integer.parseInt(stack.pop());
}
}
那么问题来了,如何把一个中缀表达式转成一个后缀表达式呢?
思路分析:
1,初始化两个栈,运算符栈s1和存储中间结果栈s2
2,从左至右扫描中缀表达式
3,遇到数字时,将其压入s2
4,遇到运算符时,比较其与s1栈顶运算符的优先级
4.1,如果s1为空,或栈顶运算符为左括号,则直接将此运算符入s1
4.2,如果运算符比栈顶的运算符优先级高,也将运算符入s1
4.3,如果运算符比栈顶的运算符优先级低,将s1的运算符弹出压入s2,再次转到4.1中与新的栈顶运算符比较
5,遇到括号时
5.1,如果是左括号,直接压入s1
5.2,如果是右括号,则依次弹出s1栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃
6,重复步骤2至5,直到表达式的最右边
7,将s1中剩余的运算符依次弹出并压入s2,
8,依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
代码实现:
public static void main(String[] args) {
List<String> list = toInfixExpressionList("1+((2+3)*4)-5");
System.out.println(list);
List<String> result = parseSuffixExpreesionList(list);
System.out.println(result);
}
/**
* 将中缀表达式转换成对应的list
*
* @param s
* @return
*/
public static List<String> toInfixExpressionList(String s) {
//定义一个list,存放中缀表达式对应的内容
List<String> list = new ArrayList<>();
int i = 0;//这是一个指针,用于遍历中缀表达式字符串
String str;//对多位数的拼接
char c;//每遍历到一个字符就放入到c中
do {
//如果c是一个非数字,加入到list中 0-9对应的Asall码是48-57
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
list.add(c + "");
i++;
} else {//如果是一个数字
str = "";
while (i < s.length() && (c = s.charAt(i)) > 48 && (c = s.charAt(i)) < 57) {
str += c;//拼接多位数
i++;
}
list.add(str);
}
} while (i < s.length());
return list;
}
/**
* 中缀表达式的list转后缀表达式list
*
* @param list
* @return
*/
public static List<String> parseSuffixExpreesionList(List<String> list) {
//定义两个栈
Stack<String> s1 = new Stack<>();//符号栈
/**
* 因为s2这个栈,在整个转换过程中,没有pop操作,而且后面还要逆序输出
* 就比较麻烦,所以使用ArrayList代替
*/
List<String> s2 = new ArrayList<>();//存储中间结果的list
//遍历list
for (String item : list) {
//如果是一个数字,加入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();//将左括号弹出,消除一堆小括号
}else{
//如果运算符比栈顶的运算符优先级低,将s1的运算符弹出压入s2,再次转到4.1中与新的栈顶运算符比较
while(s1.size() != 0 && getValue(s1.peek()) >= getValue(item)){
s2.add(s1.pop());
}
//然后将item入栈
s1.push(item);
}
}
//将s1剩余的运算符依次弹出并加入s2
while(s1.size() != 0){
s2.add(s1.pop());
}
return s2;
}
/**
* 判断运算符优先级
* @param oper
* @return
*/
public static int getValue(String oper){
int result = 0;
switch (oper){
case "+" :
result = 1;
break;
case "-" :
result = 1;
break;
case "*" :
result = 2;
break;
case "/":
result = 2;
break;
default:
System.out.println("不存在或不支持该运算符");
break;
}
return result;
}
如果需要加入小数点,可以把运算时候的数据类型改成Double,然后加入判断小数点