栈的使用-综合计算器实例

三种表达式

中缀表达式

这就是我们正常看到的表达式,操作符在操作数中间那种

前缀表达式

操作符在操作数之前,也叫波兰表达式

后缀表达式

操作符在操作数之后,叫逆波兰表达式——后两种表达式可以根据第一种进行实现,不需要括号来修改计算顺序,且后缀表达式不需要判断计算顺序,是写代码最简单的类型,因此大部分的表达式计算,会选择将其转为后缀表达式再进行计算

代码实例

这里先讲一下栈是如何用在表达式上的吧(后缀表达式)

package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator;

import java.util.*;

/**
 * 逆波兰计算器(后缀表达式)代码实现
 */
public class ReversePolishCalculator {

    /**
     * 计算一个后缀表达式的值
     *
     * @param postfixExpression
     * @return
     */
    public int cal(String postfixExpression) {
        return start(convert(postfixExpression));
    }

    /**
     * 将后缀表达式转换成 list
     *
     * @param postfixExpression 表达式中的每个元素都用空格隔开,是为了方便;这里重点不在于怎么去解析出每一个元素了
     * @return
     */
    private List<String> convert(String postfixExpression) {
        return Arrays.asList(postfixExpression.split(" "));
    }

    /**
     * 计算
     *
     * @param postfixElements
     * @return
     */
    public int start(List<String> postfixElements) {
        /*
        比如:`(3+4)x5-6` 对应的后缀表达式 `3 4 + 5 x 6 -`
        1. 从左到右扫描,将 3、4 压入堆栈
        2. 扫描到 `+` 运算符时
           将弹出 4 和 3,计算 `3 + 4 = 7`,将 7 压入栈
        3. 将 5 入栈
        4. 扫描到 `x` 运算符时
           将弹出 5 和 7 ,计算 `7 x 5 = 35`,将 35 入栈
        5. 将 6 入栈
        6. 扫描到 `-` 运算符时
           将弹出 6 和 35,计算 `35 - 6 = 29`,将 29 压入栈
        7. 扫描表达式结束,29 是表达式的值
       */
        Deque<Integer> stack = new ArrayDeque<>();
        for (String el : postfixElements) {
            // 如果是数字则入栈
            if (el.matches("\\d+")) {
                stack.push(Integer.parseInt(el));
                continue;
            }
            // 是运算符,则弹出两个数
            Integer num2 = stack.pop();
            Integer num1 = stack.pop();
            int res = cal(num1, num2, el.charAt(0));
            stack.push(res);
        }
        return stack.pop();
    }

    /**
     * 计算
     *
     * @param num1
     * @param num2
     * @param oper 操作符
     * @return
     */
    private int cal(int num1, int num2, char oper) {
        switch (oper) {
            case '+':
                return num1 + num2;
            case '-':
                return num1 - num2;
            case '*':
                return num1 * num2;
            case '/':
                return num1 / num2;
        }
        throw new IllegalArgumentException("不支持的运算符:" + oper);
    }

}

后缀表达式中,常用" "来分离数字与运算符,所以我们可以在一开始将输入的字符串根据空格进行分离,生成数组Arrays.asList(postfixExpression.split(" "));​

然后就是进行匹配,如果是数字就直接入站,结果如果是运算符就进行出栈,并出栈两个数字进行运算,将结果进行入栈

if (el.matches("\d+")) {
                stack.push(Integer.parseInt(el));
                continue;
            }
            // 是运算符,则弹出两个数
            Integer num2 = stack.pop();
            Integer num1 = stack.pop();
            int res = cal(num1, num2, el.charAt(0));
            stack.push(res); // 将计算结果入站

由于Java的栈是Java1.0版本的老东西了,建议用双端队列来代替栈

Java 官方建议用 Deque​(双端队列,如 ArrayDeque​)代替传统的 Stack​ 类,核心原因是 Stack​ 存在设计缺陷、性能问题和语义不纯粹,而 Deque​ 既满足栈的「后进先出(LIFO)」语义,又符合现代 Java 集合框架的设计规范,功能更灵活、性能更优。

中缀表达式转后缀

通过前面可以看出来,中缀表达式,人们容易看懂,但是代码写起来很麻烦,而后缀就很简单,那么如何进行中缀表达式转为后缀呢?

中缀转为后缀:

  1. 初始化两个栈:

    • 运算符栈: s1
    • 中间结果栈: s2
  2. 从左向右扫描表达式,遇到操作数,直接压入中间结果栈

  3. 如果是预算符,比较其和运算符的栈顶的优先级,如果优先级低于或等于栈顶的运算符,则让栈顶运算符弹出,压入s2中间结果栈。否则直接压入运算符栈

  4. 当栈顶是(时,直接压入运算符,当扫描到)的时候,将运算符栈的内容依次弹出,并压入中间结果栈,直至遇到左括号为止。

  5. 当扫描完成后,将运算符栈的内容依次弹出,并压入中间结果栈。


代码如下:

public ArrayList<String> infixList2SuffixList(List<String> infixList) {
        // 符号栈
        Stack<String> s1 = new Stack<>();
        // 思路是使用栈来存储表达式元素
        // 仔细观察他的解析步骤,会发现:只是在入栈,并未出现出栈操作
        // 而且,最后的结果还要逆序,所以这里使用 list,直接顺序读取出来就是最后的结果了
        ArrayList<String> s2 = new ArrayList<>();

        for (String item : infixList) {
            // 如果是数字,则加入 s2
            if (item.matches("\\d+")) {
                s2.add(item);
            }
            // 如果是左括号,直接压入 s1
            else if (item.equals("(")) {
                s1.push(item);
            }
            // 如果是右括号
            // 则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到 左括号 为止,此时将这一对括号 丢弃
            else if (item.equals(")")) {
                // 如果不是左括号,则取出 s1 中的符号,添加到 s2 中
                while (!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                // 上面循环完之后,那么就是遇到了左括号
                // 则直接弹出这个左括号丢弃
                s1.pop();
            }
            // 剩下的则是运算符
            else {
                // 如果 s1 为空,或则栈顶运算符为 (,则压入符号栈 s1
                // 如果优先级比栈顶运算符 高,则压入符号栈 s1,否则,否则将 s1 栈顶的运算符弹出,压入 s2 中
                // 上面两句话,转换成下面的描述
                // 上面如果  s1 栈顶符号优先级比 当前符号高,则弹出加入到 s2 中。
                // 因为:如果栈顶符号是 ( 返回优先级为 -1.比当前符号低,则不会走该方法
                while (!s1.isEmpty() && priority(s1.peek().charAt(0)) >= priority(item.charAt(0))) {
                    s2.add(s1.pop());
                }
                s1.push(item);
            }
        }
        // 将 s1 中的运算符依次弹出并加入 s2 中
        while (!s1.isEmpty()) {
            s2.add(s1.pop());
        }
        return s2;
    }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值