Java用栈实现计算器(中缀表达式)

一、栈的数组实现

栈是一种先进先出的数据结构,可以用数组进行模拟。下面摆出代码,ArrayStack里面有几个方法 : isEmpty(), isFull(), push(), pop(), stackList(), peek()这几个方法是属于栈的特有方法。 isOper(), priority(), calculate()是计算器的特有方法。这是栈的一些常用方法和下面计算器需要用到的方法。其中里面的几个属性比较重要,maxSize栈的大小, stack定义数组栈, top表示栈顶指针。

class ArrayStack2{
    private int maxSize;    //栈的大小
    private int[] stack;   //定义栈
    private int top = -1;    //栈顶,一定要初始化为-1,不然就有默认值0了

    //初始化栈的大小
    public ArrayStack2(int maxSize){
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }
    //栈满
    public boolean isFull(){
        return top == maxSize - 1;
    }
    //栈空
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈
    public void push(int data){
        //栈满时,不能入栈
        if(isFull()){
            System.out.println("栈满~~不能入栈");
            return;
        }
        top ++;
        stack[top] = data;
    }
    //出栈
    public int pop(){
        //栈空,不能出栈
        if(isEmpty()){
            throw new RuntimeException("栈空~~,莫得数据");
        }
        //定义中间值接收栈顶元素值
        int temp = stack[top];
        top --;
        return temp;
    }
    //输出栈(遍历栈),不过是反向遍历数组
    public void stackList(){
        //当栈空时,不需要遍历
        if(isEmpty()){
            System.out.println("栈空~莫得数据");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
    //返回栈顶的数据,但并不出栈,也就是说top指针并不移动
    public int peek(){
        return stack[top];
    }
    //判断是否是运算符
    public boolean isOper(int oper){
        return oper == '+' || oper == '-' || oper == '*' || oper == '/' || oper == '(' || oper == ')';
    }
    //加减乘除的优先级
    public int priority(int oper){
        if(oper == '*' || oper == '/'){ //优先级最高的
            return 1;
        }else if(oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;  //错误的运算符
        }
    }
    //进行运算
    public int calculate(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;
            default:
                break;
        }
        return result;
    }
}

二、栈实现计算器

本文中,用的是中缀表达式实现的计算器,可以完成多位数的基本运算,支持小括号优先级运算。

(1)、在主方法里定义数栈和符号栈

 其中,数栈存放表达式的数字部分,符号栈存放表达式的符号。

(2)、定义指针,指针指向当前表达式的最后出栈位置,初始为0

int index = 0;  //这是指针,指向当前表达式的位置

(3)、入栈

入栈的步骤会很复杂,里面写了很多if语句判断,在这里我们逐条进行分析解释。

①、首先我们需要判断当前指针所指位置的元素是数字还是符号。

②、如果是符号

i.如果符号栈为空,则直接入栈。

ii.符号栈不为空,首先判断当前元素是否为 '(' 号或者 ')'号。如果是 '('号,那直接将 '('号入符号栈。并且当前指针后移,用'continue'继续执行接下来的代码。如果是 ')'号,可知当前符号栈中必然含有 '(' 号,因为这两个符号是成对出现的。这时调用peek()方法偷看一眼符号栈栈顶元素,如果不是 '(' 号,则继续循环。含有 ')'号的执行代码如下。在最后执行完while循环后,一定要pop出符号栈中的'('符号。

                        int operation = operStack.peek();
                        while (operation != '('){
                            //小于就要从数栈中出两个数据,和栈顶元素进行运算
                            num1 = numStack.pop();
                            num2 = numStack.pop();
                            int oper = operStack.pop();
                            int result = operStack.calculate(num1, num2, oper);
                            //把运算的结果再入栈
                            numStack.push(result);
                            operation = operStack.peek();
                        }
                        operStack.pop();
                        //跳出循环
                        index ++;
                        if(index >= expression.length()){
                            break;
                        }
                        continue;

iii.在执行完上述 ii 步骤后,要继续执行接下来的代码。执行这段代码,主要是根据符号的优先级来判断的。如果当前指针所指的符号比符号栈栈顶的符号优先级低的话,就要先从数栈中弹出两个数,符号栈弹出符号进行计算,再将结果压入数栈。这样循环往复,直到符号栈中没有优先级更高的符号。最后将指针所指符号压入符号栈

while (!operStack.isEmpty() && operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        //小于就要从数栈中出两个数据,和栈顶元素进行运算
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        int oper = operStack.pop();
                        int result = operStack.calculate(num1, num2, oper);
                        //把运算的结果再入栈
                        numStack.push(result);
                    }
                    operStack.push(ch);

本来这里的while我是写成if的,不过后来发现了一个bug,改成了while。bug描述及解决思路如下:

如果当前符号优先级小于或者等于栈顶的符号,这里要反复的进行比较,
原因: 2-2*5+10 这个表达式进行运算,如果这里的最后一个'+'号,如果只与前面的'*'号比较,
那进行运算的后的结果是 2-10+10,这时'+'号入符号栈,在最后出栈进行运算时,数栈中此时
数据为 [2 10 10] 符号栈数据为 [- +],进行运算时首先出来的 是 10+10,计算结果20压入数栈
,然后是2-20,这显然不符合运算规则,用if不合适,只能用while循环。

③、如果是数字

i.如果已经到达了表达式的末尾,那就直接将指针所指数字压入数栈。

                if(index+1 == expression.length()){
                    numStack.push(ch - 48);
                }

这里之所以ch - 48,与ascii码有关,char型数据与int型数据的转换。

ii.没有到达末尾,需要判断是否是多位数。我定义了一个中间指针,初始值为当前表达式指针的下一位。定义一个变量接收中间指针所指向的值,判断这个变量是否是数字,如果是,那就进入while循环进行操作,利用字符串拼接方法拼接好多为数。最后再压入数栈。

                    //如果是多位数
                    int temp = index+1;   //获取当前表达式的下一个索引
                    char multi = expression.substring(temp, temp+1).charAt(0);  //多位数进行拼接的元素
                    String num = String.valueOf(ch);    //数字的第一个
                    while(!operStack.isOper(multi)){    //当不是运算符时,需要拼接字符串
                        num += multi;   //这里进行拼接
                        temp = temp + 1;    //下一个索引
                        index++;    //这个相应的全局指针也要后移!!!!!!!!!
                        if(temp < expression.length()){ //小于则直接拆分赋值
                            multi = expression.substring(temp, temp+1).charAt(0);
                        }else if(temp == expression.length()){  //到了表达式的末尾,直接退出循环
                            break;
                        }
                    }
                    numStack.push(Integer.parseInt(num));

④、最后记得指向表达式的指针后移

此时数栈中的栈顶元素为最终结果。

三、完整代码

package com.yc.stack;

/**
 * @author JavaJxh
 * @create 2019-11-29 20:48
 */
public class Calculator {
    public static void main(String[] args) {
        //先创建两个栈,一个存放数字,一个存放运算符
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        int index = 0;  //这是指针,指向当前表达式的位置
        int num1 = 0;
        int num2 = 0;
        String expression = "10-2-2*(5+10)+8-(21+3)*2";        //14+28=42

        //入栈
        while(1 == 1){
            char ch = expression.substring(index, index+1).charAt(0);   //得到当前指针所指的字符串内容
            //判断是符号还是数字
            if(operStack.isOper(ch)){   //如果是符号
                if(!operStack.isEmpty()){   //如果符号栈不为空
                    if(ch == '('){  //如果是'('号,则直接将'('号入栈
                        operStack.push(ch);
                        //跳出循环
                        index ++;
                        if(index >= expression.length()){
                            break;
                        }
                        continue;
                    }else if(ch == ')'){    //如果是')'号,需要将 '('号之前的东西都进行计算,将结果压入数栈
                        //返回操作栈的栈顶的数据
                        int operation = operStack.peek();
                        while (operation != '('){
                            //小于就要从数栈中出两个数据,和栈顶元素进行运算
                            num1 = numStack.pop();
                            num2 = numStack.pop();
                            int oper = operStack.pop();
                            int result = operStack.calculate(num1, num2, oper);
                            //把运算的结果再入栈
                            numStack.push(result);
                            operation = operStack.peek();
                        }
                        operStack.pop();
                        //跳出循环
                        index ++;
                        if(index >= expression.length()){
                            break;
                        }
                        continue;
                    }
                    //如果当前符号优先级小于或者等于栈顶的符号,这里要反复的进行比较,
                    //原因: 2-2*5+10 这个表达式进行运算,如果这里的最后一个'+'号,如果只与前面的'*'号比较,
                    //那进行运算的后的结果是 2-10+10,这时'+'号入符号栈,在最后出栈进行运算时,数栈中此时
                    //数据为 [2 10 10] 符号栈数据为 [- +],进行运算时首先出来的 是 10+10,计算结果20压入数栈
                    // ,然后是2-20,这显然不符合运算规则
                    while (!operStack.isEmpty() && operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        //小于就要从数栈中出两个数据,和栈顶元素进行运算
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        int oper = operStack.pop();
                        int result = operStack.calculate(num1, num2, oper);
                        //把运算的结果再入栈
                        numStack.push(result);
                    }
                    operStack.push(ch);
                }else{  //符号栈为空则直接入栈
                    operStack.push(ch);
                }
            }else{  //如果是数字,那就直接入栈
                if(index+1 == expression.length()){
                    numStack.push(ch - 48);
                }else{
                    //如果是多位数
                    int temp = index+1;   //获取当前表达式的下一个索引
                    char multi = expression.substring(temp, temp+1).charAt(0);  //多位数进行拼接的元素
                    String num = String.valueOf(ch);    //数字的第一个
                    while(!operStack.isOper(multi)){    //当不是运算符时,需要拼接字符串
                        num += multi;   //这里进行拼接
                        temp = temp + 1;    //下一个索引
                        index++;    //这个相应的全局指针也要后移!!!!!!!!!
                        if(temp < expression.length()){ //小于则直接拆分赋值
                            multi = expression.substring(temp, temp+1).charAt(0);
                        }else if(temp == expression.length()){  //到了表达式的末尾,直接退出循环
                            break;
                        }
                    }
                    numStack.push(Integer.parseInt(num));
                    //numStack.push(ch - 48); //因为是char型,char型的'1'对应ascii码表的49,所以要减48
                }

            }

            //跳出循环
            index ++;
            if(index >= expression.length()){
                break;
            }
        }

        //入栈结束后,就要逐个的进行计算了
        while (1==1){
            //当运算符栈空时,代表计算结束
            if(operStack.isEmpty()){
                int result = numStack.pop();    //这时的栈顶是计算后的结果
                System.out.printf("%s = %d", expression, result);
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            int oper = operStack.pop();
            int result = operStack.calculate(num1, num2, oper);
            //把运算的结果再入栈
            numStack.push(result);
        }
    }
}

class ArrayStack2{
    private int maxSize;    //栈的大小
    private int[] stack;   //定义栈
    private int top = -1;    //栈顶,一定要初始化为-1,不然就有默认值0了

    //初始化栈的大小
    public ArrayStack2(int maxSize){
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }
    //栈满
    public boolean isFull(){
        return top == maxSize - 1;
    }
    //栈空
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈
    public void push(int data){
        //栈满时,不能入栈
        if(isFull()){
            System.out.println("栈满~~不能入栈");
            return;
        }
        top ++;
        stack[top] = data;
    }
    //出栈
    public int pop(){
        //栈空,不能出栈
        if(isEmpty()){
            throw new RuntimeException("栈空~~,莫得数据");
        }
        //定义中间值接收栈顶元素值
        int temp = stack[top];
        top --;
        return temp;
    }
    //输出栈(遍历栈),不过是反向遍历数组
    public void stackList(){
        //当栈空时,不需要遍历
        if(isEmpty()){
            System.out.println("栈空~莫得数据");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
    //返回栈顶的数据,但并不出栈,也就是说top指针并不移动
    public int peek(){
        return stack[top];
    }
    //判断是否是运算符
    public boolean isOper(int oper){
        return oper == '+' || oper == '-' || oper == '*' || oper == '/' || oper == '(' || oper == ')';
    }
    //加减乘除的优先级
    public int priority(int oper){
        if(oper == '*' || oper == '/'){ //优先级最高的
            return 1;
        }else if(oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;  //错误的运算符
        }
    }
    //进行运算
    public int calculate(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;
            default:
                break;
        }
        return result;
    }
}

四、总结

总的来说,中缀表达式计算器的实现不是很难,但其中有不少需要注意的点。我之前按照老师的思路写,发现了bug,然后自己想了很久,终于将代码的bug改掉,同时也拓展了 '(' ')'这两个符号的运算。这让我感觉到了数据结构的魅力。我越发觉得数据结构是一个宝藏,很多问题都能用数据结构来解决。比如:我要出一个随机数,每一个随机数对应不同的人。这个功能应用于抽签,也就是说,每次随机数出的数字都不能重复,或者说如果出到重复的数字,那就再生成随机数,直到没有重复的数字为止。要是我以前,我会搞两个数组,其中一个int数组,一个String数组,String数组对应人,一个int数组对应生成的随机数,随机数的范围为String数组的大小。如果挑出来一个人,我就将其下标存入int数组,下次再挑时,对应int数组,如果里面没有当前生成的随机数,则直接挑出来,有的话则再重新生成随机数。有了数据结构后,我可能会用单链表来解决这个问题。

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
可以使用 Java 中的 Map 来实现计算器计算。具体思路如下: 1. 将所有操作符和对应的操作方法存储在 Map 中。 2. 将表达式转换为后缀表达式,并使用栈来计算后缀表达式。 下面是示例代码: ```java import java.util.*; public class Calculator { private static final Map<String, BiFunction<Double, Double, Double>> OPERATIONS = new HashMap<>(); static { OPERATIONS.put("+", (a, b) -> a + b); OPERATIONS.put("-", (a, b) -> a - b); OPERATIONS.put("*", (a, b) -> a * b); OPERATIONS.put("/", (a, b) -> a / b); } public static void main(String[] args) { String expression = "2 + 3 * 4 - 5 / 2"; List<String> postfix = toPostfix(expression); double result = evaluatePostfix(postfix); System.out.println(result); } // 将中缀表达式转换为后缀表达式 private static List<String> toPostfix(String expression) { List<String> postfix = new ArrayList<>(); Stack<String> stack = new Stack<>(); String[] tokens = expression.split("\\s+"); for (String token : tokens) { if (isNumber(token)) { postfix.add(token); } else if (OPERATIONS.containsKey(token)) { while (!stack.isEmpty() && !stack.peek().equals("(") && hasHigherPrecedence(stack.peek(), token)) { postfix.add(stack.pop()); } stack.push(token); } else if (token.equals("(")) { stack.push(token); } else if (token.equals(")")) { while (!stack.isEmpty() && !stack.peek().equals("(")) { postfix.add(stack.pop()); } stack.pop(); } } while (!stack.isEmpty()) { postfix.add(stack.pop()); } return postfix; } // 判断是否为数字 private static boolean isNumber(String token) { try { Double.parseDouble(token); return true; } catch (NumberFormatException e) { return false; } } // 判断操作符优先级 private static boolean hasHigherPrecedence(String op1, String op2) { return !op1.equals("(") && !op2.equals(")") && (op1.equals("*") || op1.equals("/")) && (op2.equals("+") || op2.equals("-")); } // 计算后缀表达式 private static double evaluatePostfix(List<String> postfix) { Stack<Double> stack = new Stack<>(); for (String token : postfix) { if (isNumber(token)) { stack.push(Double.parseDouble(token)); } else if (OPERATIONS.containsKey(token)) { double b = stack.pop(); double a = stack.pop(); stack.push(OPERATIONS.get(token).apply(a, b)); } } return stack.pop(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值