编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.用两个栈实现队列
  • 02.栈的压入弹出序列
  • 03.使用栈解析数学公式
  • 04.栈判断字符串是否回文
  • 05.使用栈实现字符串逆序
  • 06.判断括号是否匹配

03.使用栈解析数学公式

目录介绍

  • 01.题目要求
  • 02.问题分析
  • 03.实例代码
  • 04.测试代码
  • 05.优化代码

01.题目要求

  • 解析一般数学算式,实现简单的带括号的加减乘除运算。

02.问题分析

  • 解析后缀表达式(逆波兰表达式)。
    • 也即后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。(摘自百度),既然没了运算符的优先规则,那么计算机解析起来自然容易的多。
  • 对于我们常见的表达式,称为中缀表达式,每个中缀表达式都有对应的后缀表达式。如:
    • 中缀表达式:-2*(1+6/3)+4
    • 后缀表达式:-2 1 6 3 / + * 4 +(这里为了区分负号和减号,我在数字与数字、数字与符号之间都加了空格,至于怎么从中缀表达式得到后缀表达式,后面有介绍及参考程序)
  • 而在解析后缀表达式时,只需要遵守以下原则即可:
    • 从左往右遍历
    • 遇到数字直接放入容器
    • 遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器
    • 遍历结束后,容器中的数字即为运算结果
    • 按这个过程走下来,自然而然的想到用栈是最合适的。现只需想办法由输入的中缀表达式转为后缀表达式即可完成解析。
  • 由中缀表达式得到后缀表达式的算法。由中缀表达式得到后缀表达式,只要遵守以下步骤即可:
    • 首先设置运算符的优先级(这样设置也是为了简化程序):
      • ”null” 栈顶若为空,假设优先级为0
      • “(” 优先级设为1
      • “+-” 优先级设为2
      • “*/” 优先级设为3
    • 从左向右遍历中缀表达式
    • 遇到数字直接输出
    • 遇到符号
      • 遇到左括号,直接压栈
      • 遇到右括号,弹栈输出直到弹出左括号(左括号不输出)
      • 遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。
      • 最后将符合栈弹栈并输出

03.实例代码

  • 先写一个最基本的两位数四则运算方法,比较简单,没有写注释:
    private static double doubleCal(double a1, double a2, char operator) throws Exception {
        switch (operator) {
        case '+':
            return a1 + a2;
        case '-':
            return a1 - a2;
        case '*':
            return a1 * a2;
        case '/':
            return a1 / a2;
        default:
            break;
        }
        throw new Exception("illegal operator!");
    }
  • 写一个获得优先级的方法:
    private static int getPriority(String s) throws Exception {
        if(s==null) return 0;
        switch(s) {
        case "(":return 1;
        case "+":;
        case "-":return 2;
        case "*":;
        case "/":return 3;
        default:break;
        }
        throw new Exception("illegal operator!");
    }
  • 将中缀表达式转变为后缀表达式:
    private static String toSufExpr(String expr) throws Exception {
        System.out.println("将"+expr+"解析为后缀表达式...");
        /*返回结果字符串*/
        StringBuffer sufExpr = new StringBuffer();
        /*盛放运算符的栈*/
        Stack<String> operator = new Stack<String>();
        operator.push(null);//在栈顶压人一个null,配合它的优先级,目的是减少下面程序的判断
        /* 将expr打散分散成运算数和运算符 */
        Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");//这个正则为匹配表达式中的数字或运算符
        Matcher m = p.matcher(expr);
        while (m.find()) {
            String temp = m.group();
            if (temp.matches("[+\\-*/()]")) { //是运算符
                if (temp.equals("(")) { //遇到左括号,直接压栈
                    operator.push(temp);
                    System.out.println("'('压栈");
                } else if (temp.equals(")")) { //遇到右括号,弹栈输出直到弹出左括号(左括号不输出)
                    String topItem = null;
                    while (!(topItem = operator.pop()).equals("(")) {
                        System.out.println(topItem+"弹栈");
                        sufExpr.append(topItem+" ");
                        System.out.println("输出:"+sufExpr);
                    }
                } else {//遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。
                    while(getPriority(temp) <= getPriority(operator.peek())) {
                        sufExpr.append(operator.pop()+" ");
                        System.out.println("输出sufExpr:"+sufExpr);
                    }
                    operator.push(temp);
                    System.out.println("\""+temp+"\""+"压栈");
                }
            }else {//遇到数字直接输出
                sufExpr.append(temp+" ");
                System.out.println("输出sufExpr:"+sufExpr);
            }
    
        }
    
        String topItem = null;//最后将符合栈弹栈并输出
        while(null != (topItem = operator.pop())) {
            sufExpr.append(topItem+" ");
        }
        return sufExpr.toString();
    }
  • 解析中缀表达式的方法
    public static String getResult(String expr) throws Exception {
        String sufExpr = toSufExpr(expr);// 转为后缀表达式
        System.out.println("开始计算后缀表达式...");
        /* 盛放数字栈 */
        Stack<Double> number = new Stack<Double>();
        /* 这个正则匹配每个数字和符号 */
        Pattern p = Pattern.compile("-?\\d+(\\.\\d+)?|[+\\-*/]");
        Matcher m = p.matcher(sufExpr);
        while (m.find()) {
            String temp = m.group();
            if (temp.matches("[+\\-*/]")) {// 遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器
                System.out.println("符号"+temp);
                double a1 = number.pop();
                double a2 = number.pop();
                double res = doubleCal(a2, a1, temp.charAt(0));
                number.push(res);
                System.out.println(a2 + "和" + a1 + "弹栈,并计算" + a2 + temp + a1);
                System.out.println("数字栈:" + number);
            } else {// 遇到数字直接放入容器
                number.push(Double.valueOf(temp));
                System.out.println("数字栈:" + number);
            }
        }
        return number.pop() + "";
    }

04.测试代码

  • 以-3.5*(4.5-(4+(-1-1/2)))测试,如下所示
    public void test{
        String str = "-3.5*(4.5-(4+(-1-1/2)))";
        System.out.println(getResult(str));
    }

05.优化代码

  • 简化思路
    • 根据这个算法,在不需要解出后缀表达式的情况下,还可以将代码进一步简化。在解析的过程的中,我们只需要按照以下原则:
    • 使用两个栈,一个数字栈,一个符号栈
    • 从左往右遍历表达式字符串
    • 遇到数字,直接压入数字栈
    • 遇到符号
      • 遇到左括号,直接入符号栈
      • 遇到右括号,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至取栈顶为左括号,将左括号弹出
      • 遇到运算符,1)若该运算符的优先级大于栈顶元素的优先级,直接入符号栈。2)若小于,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至该运算符的优先级大于符号栈顶元素的优先级,然后将该符号入符号栈
    • 遍历结束后,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至符号栈无符号(或数字栈只有一个元素),则数字栈的元素为运算结果
  • 简化代码
    public static String getResult(String expr) throws Exception {
        System.out.println("计算"+expr);
        /*数字栈*/
        Stack<Double> number = new Stack<Double>(); 
        /*符号栈*/
        Stack<String> operator = new Stack<String>();
        operator.push(null);// 在栈顶压人一个null,配合它的优先级,目的是减少下面程序的判断
    
        /* 将expr打散为运算数和运算符 */
        Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");// 这个正则为匹配表达式中的数字或运算符
        Matcher m = p.matcher(expr);
        while(m.find()) {
            String temp = m.group();
            if(temp.matches("[+\\-*/()]")) {//遇到符号
                if(temp.equals("(")) {//遇到左括号,直接入符号栈
                    operator.push(temp);
                    System.out.println("符号栈更新:"+operator);
                }else if(temp.equals(")")){//遇到右括号,"符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈",重复引号步骤至取栈顶为左括号,将左括号弹出
                    String b = null;
                    while(!(b = operator.pop()).equals("(")) {
                        System.out.println("符号栈更新:"+operator);
                        double a1 = number.pop();
                        double a2 = number.pop();
                        System.out.println("数字栈更新:"+number);
                        System.out.println("计算"+a2+b+a1);
                        number.push(doubleCal(a2, a1, b.charAt(0)));
                        System.out.println("数字栈更新:"+number);
                    }
                    System.out.println("符号栈更新:"+operator);
                }else {//遇到运算符,满足该运算符的优先级大于栈顶元素的优先级压栈;否则计算后压栈
                    while(getPriority(temp) <= getPriority(operator.peek())) {
                        double a1 = number.pop();
                        double a2 = number.pop();
                        String b = operator.pop();
                        System.out.println("符号栈更新:"+operator);
                        System.out.println("数字栈更新:"+number);
                        System.out.println("计算"+a2+b+a1);
                        number.push(doubleCal(a2, a1, b.charAt(0)));
                        System.out.println("数字栈更新:"+number);
                    }
                    operator.push(temp);
                    System.out.println("符号栈更新:"+operator);
                }
            }else {//遇到数字,直接压入数字栈
                number.push(Double.valueOf(temp));
                System.out.println("数字栈更新:"+number);
            }
        }
    
        while(operator.peek()!=null) {//遍历结束后,符号栈数字栈依次弹栈计算,并将结果压入数字栈
            double a1 = number.pop();
            double a2 = number.pop();
            String b = operator.pop();
            System.out.println("符号栈更新:"+operator);
            System.out.println("数字栈更新:"+number);
            System.out.println("计算"+a2+b+a1);
            number.push(doubleCal(a2, a1, b.charAt(0)));
            System.out.println("数字栈更新:"+number);
        }
        return number.pop()+"";
    }
贡献者: yangchong211
上一篇
02.栈的压入弹出序列
下一篇
04.栈判断字符串是否回文