栈实现综合计算器

栈实现综合计算器

1. 中缀表达式

  • 中缀表达式就是平时的式子计算,如:3*2+2、2-1+3/3

  • 使用栈完成中缀表达式的计算思路分析

    1. 通过一个index索引值,遍历表达式
    2. 如果发现扫描到的是一个数字,就直接加入数栈
    3. 如果发现扫描到的是一个符号,分三种情况:
      1. 当前符号栈空,则直接入栈
      2. 当前符号栈有操作符,就进行比较:
        1. 如果当前的操作符优先级小于或等于符号栈中的操作符,则从数栈中pop取出两个数,和从符号栈中pop取出一个操作符,进行运算,然后将运算得到的结果放回数栈中,和将当前要入栈的操作符(那个优先级较大的)入栈,并把运算过的运算符移除掉
        2. 反之,如果当前的操作符优先级大于符号栈的操作符,则直接符号栈
    4. 当表达式扫描完后,就顺序的从数栈和符号栈中pop出相应的数和符号,后pop的数减去先pop的数,然后将运算结果放回数栈
    5. 最后在数栈中只有一个数字,就是表达式的结果

代码实现

  • 先创建一个数组模拟栈
//定义一个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 -

  • 这里先分析如何实现逆波兰表达式的计算,具体的思路步骤如下:

    1. 从左到右扫描,将3和4压入堆栈
    2. 遇到 + 运算符,弹出4 和3 ,这里4是栈顶元素、3是次顶元素,计算机出3+4的值,得到7,将其放入栈
    3. 将5入栈
    4. *运算符,弹出5 和7 ,计算出得35,将35入栈
    5. 将6入栈
    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 -

  • 思路分析:前人总结,后人乘凉

    1. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;

    2. 从左至右扫描中缀表达式;

    3. 遇到数字时,将其压入s2;

    4. 遇到运算符时,比较其与s1栈顶运算符的优先级:

      1. 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
      2. 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
      3. 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较;
    5. 遇到括号时:
      (1) 如果是左括号“(”,则直接压入s1
      (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

    6. 重复步骤2至5,直到表达式的最右边

    7. 将s1中剩余的运算符依次弹出并压入s2

    8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

  1. 在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]

  1. 将得到的中缀表达式对应的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;
}
  1. 由后缀表达式对应的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

posted @   gdxstart  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示