【数据结构】求字符串类型数学表达式的值

一、前言

最近在开发中遇到一个场景,用户输入一个字符串,这个字符串是一个数学计算式,让求出计算的结果
支持

  • 四则运算
  • 多位小数、整数
  • 大中小括号

久闻大佬们经常提起,可以利用栈将一个字符串形式的数学表达式,计算得出最终的结果,于是乎,小编利用自己的一把头发,终于将其实现了出来。

二、问题

计算器这个东西,相信不管是女士们,先生们,老人,孩子,还是叔叔大爷应该都有听过,都见过,都用过,大家可能会有下面这些问题:
问:计算器,大家都用过啊,1+1=2,继续在开发中,sum=number1+number,每天都在用啊。
答:没错,就是常说的最简单的计算器,只不过您提到的sum=number1+number,其中的三个变量都是有自己的类型的,本文讲的是表达式完全是一个字符串。

问:都是字符串那也简单啊,字符串拆解,然后挨个计算呗!
答:没错,的确是需要字符串拆解。但是数学计算中,运算符是有优先级的,例如乘法比加法优先级高,而且每个数字可能由1,2,3,4…等多位数组成,还可以有小数点,那如何挨个算?

问:
在这里插入图片描述

答:好的,接下来,就和大家一起学习,如何用栈实现计算器

三、餐前开胃

如果您对栈还不是很了解,可以看一看笔者之前的博客【数据结构】不看会后悔版本的单链表实现栈 ,了解一下栈,以及如何通过单链表实现栈。

四、源码地址

伙伴们,吃水不忘挖井人,大家start点起来

https://gitee.com/sunshineAndDream/data-structures-arithmetic.git

五、思路:

  • 由于拿到的是用户输入的一个字符串,所以首先,我们应该先拿到字符串的每一个元素
  • 需要准备两个栈,一个存放数字,一个存放运算符号
  • 取出字符串中的数字和符号,分别放入到对应的栈

1、数字栈需要注意的问题

当读取到一个字符是数字时,不能立刻压入栈中,因为可能是一个多位整数,例如:11,222,3333,也可能是一个小数,例如:3.14,所以我们需要继续读,直到下一位不是0到9的数字,且不是小数点为止。

2、符号栈需要注意的问题

  • 当读取到一个字符是符号时,需要看一下栈中是否有符号
    • 如果符号栈中没有元素或者当前符号是左半括号:({[() ,则直接将符号放入到栈中
    • 否则:
      • 如果当前符号是右半括号:(}])) ,则循环出栈,并计算数值,直到找到当前括号的另一半
      • 然后判断当前运算符和现在栈顶的运算符的优先级
        • 如果当前的优先级小于等于符号栈栈顶的优先级,则需要取出符号栈的一个元素,数字栈的两个元素进行运算
          • 如果此时是减法或者除法,需要让第二个出栈的元素减去(除以)第一个出栈的元素。
        • 如果当前的优先级大于符号栈栈顶符号的优先级,则直接压入栈
          解析

问:为什么是小于等于需要计算,而大于直接入栈?
答:在数学中,是从左往右计算,但是存入到栈中,顺序恰好相反;因为将所有的数据都入栈结束以后,会将所有元素挨个出栈计算:

  • 如果当前符号的优先级大于前一个优先级,那么因为后进先出,所以计算优先级高,也是先计算、
  • 如果当前符号的优先级小于等于前一个优先级,那么因为后进先出,并且数学计算是从左到右,所以需要提前计算,否则不符合数学计算的规则

问:为什么减法(除法)需要让第二个出栈的元素减去(除以)第一出栈的元素?
答:因为栈是后进先出,是从表达式头部开始入栈,例如表达式:3-2,数字栈中,先入栈3,在入栈2,此时顺序出现了颠倒,所有需要颠倒一下。因为乘法和出发

问:为什么加法(乘法)没有影响
答:因为加法(乘法)两个元素,交换位置结果不变。

六、流程图

在这里插入图片描述

请添加图片描述

七、核心代码

如果需要全部代码,欢迎去gitee上拉


/**
 * 计算器类
 */
class CalculatorString{

    //定义数字的栈
    private ArrayStack<Double> numberStack = new ArrayStack<>(100);

    private ArrayStack<Character> operationStack = new ArrayStack<>(100);
    public void calculator(String expression){
        //region 一、变量定义
        //定义字符串索引
        int index = 0;
        //定义字符串重组,用来解决多位数问题
        StringBuilder keepString = new StringBuilder();
        //endregion

        //region 二、核心逻辑
        //遍历需要计算的字符串
        while (index < expression.length()){
            if (index == 25){
                System.out.println("开始了");
            }
            System.out.println(index);
            //获取到需要计算字符串的第index(默认从0开始)个字符
            char c = expression.substring(index, index + 1).charAt(0);
            //如果是数字
            if(CalculatorUtils.isNumber(c)){
                //如果已经遍历到最后一位,则直接加入到栈
                if(index == expression.length()-1){
                    numberStack.push((double) (c-48));
                } else {
                    //如果不是是最后一位
                    //先将这位数字添加到keepString字符串中
                    keepString.append(c);
                    //获取到index的下一位
                    c = expression.substring(index + 1, index + 2).charAt(0);
                    //如果c是数字(此时的c已经是第二位数字),则证明此时的数字不是个位数,继续遍历
                    while (CalculatorUtils.isNumber(c)){
                        //将第二位数字添加到其中
                        keepString.append(c);
                        //索引移动
                        index++;
                        //获取到下一位,然后继续判断你
                        if(index == expression.length()-1){
                            break;
                        }
                        c = expression.substring(index + 1, index + 2).charAt(0);
                    }
                    //运行到这里,即下一位肯定不是数字,即已经读取完改数
                    numberStack.push(Double.parseDouble(keepString.toString()));
                    //清空keepString
                    keepString.delete(0,keepString.length());
                }
            } else if(CalculatorUtils.isOper(c)) {
                //如果是符号
                //1.如果栈中是空的或者是左侧符号,则直接加入到栈
                if(operationStack.isEmpty() || CalculatorUtils.isLeftBracket(c)){
                    operationStack.push(c);
                } else {
                    //2.如果是右侧括号,则需要计算
                    if(CalculatorUtils.isRightBracket(c)){
                        //看一看当前符号栈中的内容,是看一看,不是出栈
                        Character lastOperation = operationStack.peek();
                        //直到找到和自己同级的符号
                        while (CalculatorUtils.bracketEquals(lastOperation) != CalculatorUtils.bracketEquals(c)){
                            //调用方法,出栈两个数,一个符号,结算出结果,并将结果压入栈中
                            numberStack.push(popAndCalculator());

                            //移动到下一个
                            lastOperation = operationStack.peek();
                        }
                        //因为上面是一直出栈,直到两个符号同级别的时候结束while,但是此时的同级别符号已经出栈
                        Character peek = operationStack.pop();

                    }
                    //3.如果栈不是空的,且不是括号,取出元素,判断和当前字符的优先级
                    //现在的运算符级别
                    //之前的运算符级别
                    //如果现在的小于等于之前的,则计算
                    else if(CalculatorUtils.priority(c) <= CalculatorUtils.priority(operationStack.peek())){
                        double cal = popAndCalculator();
                        numberStack.push(cal);
                        operationStack.push(c);
                    } else {
                        operationStack.push(c);
                    }
                }

            }
            index ++ ;
        }
        //endregion

        System.out.println("开始计算");
        //region 三、最后计算
        while (true){
            if(numberStack.getTop() == 0 ){
                System.out.println(numberStack.pop());
                break;
            }
            double lastNumber = numberStack.pop();
            double beforeLastNumber = numberStack.pop();
            Character lastOperation = operationStack.pop();
            double cal = CalculatorUtils.cal(lastNumber, beforeLastNumber, lastOperation);
            numberStack.push(cal);
        }
        //endregion

    }


    /**
     * 出栈两个符号元素,并计算
     * @return
     */
    private double popAndCalculator(){
        double lastNumber = numberStack.pop();
        double beforLastNumber = numberStack.pop();
        double cal = CalculatorUtils.cal(lastNumber, beforLastNumber, operationStack.pop());
        return cal;
    }

}

八、总结

  • 通过对实例的应用,发现原来人尽皆知的栈,竟然会有这么牛的用法。
  • 通过这个例子,应该就是windows底层对计算器实现的一个简易版本。
  • 其实这个例子是对学习资料的一个总结,只不过小编觉得老师实现的代码有部分缺陷,所以笔者对其进行了修改。
  • 程序及人生,突然发现数据结构也挺好玩的!!!

好了,又要说再见的时间了,今天的内容,你学废了吗?

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值