栈实现综合计算器
栈实现综合计算器
1. 中缀表达式
-
中缀表达式就是平时的式子计算,如:3*2+2、2-1+3/3
-
使用栈完成中缀表达式的计算思路分析
- 通过一个index索引值,遍历表达式
- 如果发现扫描到的是一个数字,就直接加入数栈
- 如果发现扫描到的是一个符号,分三种情况:
- 当前符号栈空,则直接入栈
- 当前符号栈有操作符,就进行比较:
- 如果当前的操作符优先级小于或等于符号栈中的操作符,则从数栈中pop取出两个数,和从符号栈中pop取出一个操作符,进行运算,然后将运算得到的结果放回数栈中,和将当前要入栈的操作符(那个优先级较大的)入栈,并把运算过的运算符移除掉
- 反之,如果当前的操作符优先级大于符号栈的操作符,则直接符号栈
- 当表达式扫描完后,就顺序的从数栈和符号栈中pop出相应的数和符号,后pop的数减去先pop的数,然后将运算结果放回数栈
- 最后在数栈中只有一个数字,就是表达式的结果
代码实现
- 先创建一个数组模拟栈
//定义一个ArrayStack表示栈 class ArrayStack{ private int maxSize; private int[] stack; private int top=-1; //构造器 public ArrayStack(int maxSize) { this.maxSize = maxSize; stack=new int[this.maxSize];//初始化数组 } //栈满 public boolean isFull(){ return top==maxSize-1; } //栈空 public boolean isEmpty(){ return top==-1; } //入栈 public void push(int value){ if (isFull()){ System.out.println("栈满"); return; } top++; stack[top]=value; } //出栈 public int pop(){ if (isEmpty()){ throw new RuntimeException("栈空"); } int value=stack[top]; top--; return value; } //显示栈,遍历时从栈顶开始显示数据 public void showStack(){ if (isEmpty()){ System.out.println("栈空,无数据"); return; } for (int i= top;i>-1;i--){ System.out.printf("stack[%d]:%d\n",i,stack[i]); } } }
- 再写几个方法,方便实现逻辑调用判断
/** * 根据输入一个运算符,判断其优先级,如果参数优先级大于符号栈中的运算符,则直接入符号栈, * 否则,从数栈拿出两数,和从符号栈中拿出符号,进行运算,将计算结果放入数栈, * 将要加入的运算符替换掉用来运算的符号,并放入符号栈 * @param opera 传入一个运算字符 * @return 返回一个数字代表运算符的优先级 */ public int priority(int opera){ if (opera=='*'||opera=='/'){ return 1; }else if (opera=='+'||opera=='-'){ return 2; }else { return -1; } } /** * 判断是否该值是不是运算符 * @param value 传入一个运算表达式的一个值 * @return 返回true,则是一个运算符 */ public boolean isOpera(int value){ return value=='*'||value=='/'||value=='+'||value=='-'; } /** * 计算方法,根据运算符的优先级进行出栈 * @param num1 数字1 * @param num2 数字2 * @param opera 运算符 * @return 返回计算后的结果 */ public int cal(int num1,int num2,int opera){ int result =0;//声明变量result,方便将计算结果保存 switch (opera){ case '+': result = num1 + num2; break; case '-': result =num2 - num1; break; case '*': result = num1*num2; break; case '/': result = num2/num1; default: break; } return result; }
- main方法里具体实现逻辑判断
package com.guodaxia.stack; /** * @ author Guo daXia * @ create 2022/11/18 */ public class Calculator { public static void main(String[] args) { //先静态判断 一个表达式 的逻辑运算 String expression ="3+2*3-2"; //创建两个栈,数栈和符号栈 ArrayStack numStack = new ArrayStack(10); ArrayStack operaStack = new ArrayStack(10); //定义需要的相关变量 int index = 0;//用于扫描 int num1 = 0; int num2 = 0; int opera = 0; int result = 0; char ch = ' ';//将每次扫描后得到的char保存到ch中 //使用while循环扫描expression while (true) { //依次得到 每一个字符 ch = expression.substring(index, index + 1).charAt(0); //判断ch是什么,然后做相应的处理 if (operaStack.isOpera(ch)) {//如果是运算符的话 //判断当前符号栈是否为空 if (!operaStack.isEmpty()) {//如果当前符号栈不空 //判断当前运算符的优先级 int str = operaStack.peek();//得到符号栈中运算符 if (operaStack.priority(ch) <= operaStack.priority(str)) {//如果要进栈的符号优先级小于str //数栈pop出两个数,符号栈pop出str运算符,进行计算,并将结果在数栈里,将要进栈的符号进栈 num1 = numStack.pop(); num2 = numStack.pop(); opera = operaStack.pop(); result = numStack.cal(num1, num2, opera); numStack.push(result); operaStack.push(ch); } else { operaStack.push(ch); } } else { operaStack.push(ch); } }else {//不是运算符,是数字 numStack.push(ch -48);//底层编码是Ascii } //让index + 1,判断是否扫描到expression最后 index++; if (index>=expression.length()){ break; } } //当扫描完expression表达式后,就顺序的从数栈和符号栈中pop出,进行运算 while (true){ if (operaStack.isEmpty()){//如果符号栈为空,则计算到最后的结果,数栈中只剩一个数字,那就是运算答案 break; } num1 = numStack.pop(); num2 = numStack.pop(); opera = operaStack.pop(); result = numStack.cal(num1, num2, opera); numStack.push(result); } System.out.printf("表达式%s = %d",expression,numStack.pop()); } }
- 运行完前面的代码后,发现2位数无法进行运算!在扫描完一个数字后还要判断下一位是不是数字,如果是,则拼接,并继续扫描下一位符号,如果是数字,继续拼接,直到不是数字再将拼接后的数放到数栈;如果不是数字,则直接入栈
//不是运算符,是数字 // numStack.push(ch -48);//底层编码是Ascii //改良成10位数进行运算 keepNum += ch; //用于拼接,数字+数字(运算符) if (index==expression.length()-1){//说明来到了最后一个位置了,直接进栈 numStack.push(Integer.parseInt(keepNum)); }else { //如果后一位是运算符,则入栈 if (operaStack.isOpera(expression.substring(index+1,index+2).charAt(0))){ numStack.push(Integer.parseInt(keepNum)); //最后将keepNum清空 keepNum=""; //不能有空格 } } }
2. 后缀表达式
-
后缀表达式也叫逆波兰表达式。
-
(3+4)*5-6的逆波兰表达式格式是:3 4 + 5 * 6 -
-
这里先分析如何实现逆波兰表达式的计算,具体的思路步骤如下:
- 从左到右扫描,将3和4压入堆栈
- 遇到 + 运算符,弹出4 和3 ,这里4是栈顶元素、3是次顶元素,计算机出3+4的值,得到7,将其放入栈
- 将5入栈
- *运算符,弹出5 和7 ,计算出得35,将35入栈
- 将6入栈
- -运算符,弹出6 和35 ,计算机出得29,由此得到最终答案
-
代码实现
package com.guodaxia.computer; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * @ author Guo daXia * @ create 2022/11/19 */ public class PolandNotation { public static void main(String[] args) { //定义逆波兰表达式,这里采用空格隔开数字和符号 String suffixExpression = "3 4 + 5 * 6 - "; //具体实现思路 //1.将表达式放到ArrayList集合中 //2.将ArrayList集合传递给一个方法,通过遍历ArrayList 及配合栈 完成计算 List<String> list = getListString(suffixExpression); System.out.println("rpnList=" + list); int res = car(list); System.out.println("计算的结果是="+res); } //传递一个表达式 作为 形参,将后缀表达式保存在ArrayList中 public static List<String> getListString(String suffixExpression){ String[] split = suffixExpression.split(" "); ArrayList<String> list = new ArrayList<>(); for (String element:split) { list.add(element); } return list; } //完成对逆波兰表达式的运算,即按照思路步骤完成 public static int car(List<String> ls){ //只创建一个栈 Stack<String> stack = new Stack<>(); //遍历ls for (String ele:ls) { if (ele.matches("\\d+")){//使用正则表达式,如果遍历到的ele是数字 //入栈 stack.push(ele); }else { //pop出两个数字,进行运算 int num2 = Integer.parseInt(stack.pop()); int num1 = Integer.parseInt(stack.pop()); int result = 0; if (ele.equals("+")){//这里采用if-else结构实现分支计算 result=num1+num2; }else if (ele.equals("-")){ result=num1-num2; }else if (ele.equals("*")){ result=num1*num2; }else if (ele.equals("/")){ result=num1/num2; }else { throw new RuntimeException("不是运算符"); } //将result放回栈 stack.push(result+""); } } //最后存在栈里的就是答案 return Integer.parseInt(stack.pop()); } }
3. 中缀转后缀表达式
-
中缀表达式 1+((2+3)*4)-5》 转后缀表达式 - 5 + \ 4 + 3 2 1 => 1 2 3 + 4 + 5 -
-
思路分析:前人总结,后人乘凉
-
初始化两个栈:运算符栈s1和储存中间结果的栈s2;
-
从左至右扫描中缀表达式;
-
遇到数字时,将其压入s2;
-
遇到运算符时,比较其与s1栈顶运算符的优先级:
- 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
- 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
- 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较;
-
遇到括号时:
(1) 如果是左括号“(”,则直接压入s1
(2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃 -
重复步骤2至5,直到表达式的最右边
-
将s1中剩余的运算符依次弹出并压入s2
-
依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
-
- 在2.后缀表达式的代码基础上,继续添加一个方法,用于将中缀表达式存放在ArrayList中
- 代码实现存放到ArrayList
//将中缀表达式转成对应的list public static List<String> toInfixExpression(String s){ //定义一个List,存放中缀表达式 ArrayList<String> ls = new ArrayList<>(); //指针,用于遍历表达式 int i = 0; String str;//对多位数进行拼接 char c;//每遍历一个字符,就放入到c中 do { if ((c=s.charAt(i))<48||(c=s.charAt(i))>57){//如果c不是一个数子 ls.add(""+c); i++; }else {//如果是一个数,需要考虑多位数 //先将str置空 str=""; while (i<s.length()&&(c=s.charAt(i))>=48&&(c=s.charAt(i))<=57){ // 拼接 str +=c; i++; } ls.add(str); } }while (i<s.length()); return ls; }
- 在main方法中测试
String expression= "1+((2+3)*4)-5"; List<String> list = toInfixExpression(expression); System.out.println(list);
- 测试结果
*[1, +, (, (, 2, +, 3, ), , 4, ), -, 5]
- 将得到的中缀表达式对应的List=>后缀表达式对应的List
- 代码实现转换
//将中缀表达式转成对应的list,因为要利用list集合的方便 public static List<String> toInfixExpression(String s){ //定义一个List,存放中缀表达式 ArrayList<String> ls = new ArrayList<>(); //指针,用于遍历表达式 int i = 0; String str;//对多位数进行拼接 char c;//每遍历一个字符,就放入到c中 do { if ((c=s.charAt(i))<48||(c=s.charAt(i))>57){//如果c不是一个数子 ls.add(""+c); i++; }else {//如果是一个数,需要考虑多位数 //先将str置空 str=""; while (i<s.length()&&(c=s.charAt(i))>=48&&(c=s.charAt(i))<=57){ // 拼接 str +=c; i++; } ls.add(str); } }while (i<s.length()); return ls; }
- 由后缀表达式对应的list =>后缀表达式
//传入一个中缀表达式list,转化成后缀表达式 public static List<String> parseSuffixExpression(List<String> ls){ //1.定义两个栈,一个符号栈 // 一个中间存储栈,用于存后缀表达式的元素,最后因为要逆序取出所有元素 //可以使用List集合的add方法存储元素,最后顺序取出,即为后缀表达式 Stack<String> s1= new Stack<>();//符号栈 List<String> s2 = new ArrayList<>();//中间储存地 //2.遍历中缀表达式的每个item,然后依次判断何种元素,进行不同操作 for (String item :ls) { //2.1如果是一个数,则加入到s2 if (item.matches("\\d+")){ s2.add(item); }else if (item.equals("(")){ //2.2如果是(左括号,则总结入符号栈 s1.push(item); }else if (item.equals(")")){ //2.3如果是)右括号,则从s1中弹出一个符号,并加入到s2中,直到遇到(左括号为止,然后将一对括号丢弃 while (!s1.peek().equals("(")){ s2.add(s1.pop()); } s1.pop();//从s1中丢弃左括号,右括号没加入任何栈,故没有丢弃一说 }else { //2.4判断优先级,如果item的优先级小于等于s1栈运算符,则将s1栈顶的运算符弹出并加入到s2中, // 再次跟s1中新的栈顶运算符进行比较, 最后把item入s1 while (s1.size()!=0&&Operation.getValue(s1.peek())>=Operation.getValue(item)){ s2.add(s1.pop()); } s1.push(item); } } //3.将s1剩余的运算符依次弹出并加入到s2 while (s1.size()!=0){ s2.add(s1.pop()); } return s2; }
//编写一个类,用于判断运算符的优先级,并将其返回 class Operation{ private static final int ADD=1; private static final int SUB=1; private static final int MUL=2; private static final int DIV=2; /** * 根据运算符优先级返回一个对应的数字 * @param operation 传入一个运算符 * @return 返回一个对应的数字 */ public static int getValue(String operation){ int res = 0; switch (operation){ case "+": res = ADD; break; case "-": res = SUB; break; case "*": res = MUL; break; case "/": res = DIV; break; default: break; } return res; } }
- 最后在main方法测试将前缀表达式 => 后缀表达式 =>计算出答案
public class PolandNotation { public static void main(String[] args) { String expression= "1+((2+3)*4)-5"; List<String> infixExpression = toInfixExpression(expression); System.out.println("中缀表达式对应的list:"+infixExpression); List<String> suffixExpression= parseSuffixExpression(infixExpression); System.out.println("后缀表达式对应的list:"+suffixExpression); int result = car(suffixExpression); System.out.printf("由后缀表达式计算中缀表达式 %s=%d\n",expression,result); } }
- 控制面板输出:
中缀表达式对应的list:[1, +, (, (, 2, +, 3, ), , 4, ), -, 5]
后缀表达式对应的list:[1, 2, 3, +, 4, * , +, 5, -]
由后缀表达式计算中缀表达式 1+((2+3)4)-5=16
比任何人都要努力
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)