栈实现综合计算器

栈实现综合计算器

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 @ 2022-11-20 15:36  郭培鑫同学  阅读(23)  评论(0编辑  收藏  举报