数据结构小白之如何从零写一个简单的逆波兰计算器

从三种表达式说起

前缀表达式介绍

1.前缀表达式(波兰表达式)
*前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
*:(3+4)*5-6(中缀表达式) 对应的前缀表达式就是- X +3 4 5 6

 前缀表达式在计算机中求值:
    从右到左进行表达式的扫描,遇到数字时,将数字压入堆栈。遇到运算符时,弹出栈顶的两个数,用运算符对它们做计算(栈顶元素和栈底元素),然后将结果入栈
    重复上述结果得到最终的结果
中缀表达式介绍

*中缀表达式就是常见的运算表达式 例如:(3+4)*5+6
*但是对于计算机来说,直接操作中缀表达式并不好操作,往往会把中缀表达式变成其他的(一般变为后缀表达式)

后缀表达式介绍

后缀表达式又称逆波兰表达式,和前缀表达式类似,只是运算符位于操作数之后
类似: (3+4)5-6–>34+56-

后缀表达式在计算机中的求值:
        从左到右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符的时候,弹出栈顶两个数进行运算 将结果入栈 重复上述结果得到最终的结果
如何写一个简单的逆波兰计算器

part 1.有栈的基础知识(为了方便在这里使用系统的Stack类)
part 2.知道如何将中缀表达式转换为后缀表达式()
part 3.知道如何进行后缀表达式的计算

代码效果: 输入一串中缀表达式,通过算法(1)转换为后缀表达式,然后通过算法(2)计算出结果

part1: 可以看之前一篇文章,总结了一下栈的基础

part2:中缀表达式转为后缀表达式
思路:

如何将中缀表达式转成后缀表达式?

具体步骤如下:
1.初始化两个栈,运算符栈s1和储存中间结果的栈s2(出于实际情况考虑,可以使用ArrayList)
2.从左到右扫描中缀表达式
3.遇到操作数的压s2栈
4.遇到运算符,则比较和s1栈顶运算符的优先级
    4-1:如果s1为空,或者栈顶运算符为左括号'(',则直接压s1
    4-2:否则,若优先级比栈顶运算符高,直接压s1
    4-3:否则,将s1栈顶的运算符弹出并压入s2,重复s1比较

5.遇到括号时:
    5-1:如果是左括号,直接压入s1
    5-2:如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

6.重复步骤2-5 直到扫描到表达式的最右边
7.将s1中剩余的运算符依次弹出并压入s2
8.依次弹出s2中的元素并输出,结果的逆序就是中缀表达式对应的后缀表达式


ep:中缀表达式: 1+((2+3)*4)-5 如何进行转变为后缀
    1.初始化两个栈 s1(符号栈) s2(操作数栈)
    2.开始进行扫描
        ...

        s1: + ( ( + )
        s2: 1 2 3
        //已经扫描到了3后面的右括号
        //此时出现第一个变化 注:括号不算是运算符

        s1: + (
        s2: 1 2 3 +

        //继续扫描
        s1: + ( *
        s2: 1 2 3 + 4
        //由于 * 的优先级比+高 所以可以直接进行压栈

        //继续扫描
        s1: +
        s2: 1 2 3 + 4 *
        //这时候已经扫描到了4后的括号

        //继续扫描
        s1: + -
        s2: 1 2 3 + 4 *
        !!! 由于+ - 的优先级一致 弹出当前s1栈顶 并且压入s2
        s1: -
        s2: 1 2 3 + 4 * +

        //继续扫描
        s1: -
        s2: 1 2 3 + 4 * + 5

        //已经扫描到了最右边,将s1剩下的运算符弹出 并且压入s2中
        s1: null
        s2: 1 2 3 + 4 * + 5 -

        //最后逆序输出 得到最后的答案
        中缀表达式: 1+((2+3)*4)-5
        后缀表达式: 1 2 3 + 4 * + 5 -

代码实现:

//将中缀表达式转化为后缀表达式
    public static List<String> parseSuffixExpressionList(List<String> ls){
        //定义
        Stack<String> s1=new Stack<String>();//符号栈
        //由于在s2栈中,在整个转换过程中,没有pop的操作,而且后面还需要逆序输出。因此比较麻烦,所以不使用栈,直接使用List进行替代
        List<String> s2=new ArrayList<String>();

        //遍历ls
        for (String item : ls) {
            //如果是一个数 加入到s2
            if(item.matches("\\d+")){
                s2.add(item);
            }
            //如果是左括号,就直接入到s1
            else if(item.equals("(")) {
                s1.push(item);
            }
            //如果是右括号 依次弹出s1栈顶的运算符,并且压入s2,直到遇到左括号为止,此时将一对括号丢弃
            else if(item.equals(")")){
                while(!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();//将左括号弹出s1【消除括号】
            }

            //考虑符号的优先级问题
            else{
                //当item的优先级小于等于s1栈顶运算符的优先级,将s1栈顶的运算符弹出加入s2中
                //缺少方法来判断优先级
                while(s1.size()!=0 && Operation.getValue(s1.peek())>=Operation.getValue(item)){
                    s2.add(s1.pop());
                }
                //遍历后还需要将item压入栈中
                s1.push(item);
            }
            //遍历已经结束了 将s1中剩余运算符加入s2中
        }
        while(s1.size()!=0){
            s2.add(s1.pop());
        }
        //因为是存放到list中,所以无需进行逆转
        return s2;
    }

part3 基于后缀表达式的计算器实现:
思路:

从左到右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符的时候,弹出栈顶两个数进行运算 将结果入栈 重复上述结果得到最终的结果

代码实现:

  //开始对逆波兰表达式的运算
    public static int calculate(List<String> ls) {
        //只需要一个栈即可
        Stack<String> stack = new Stack<>();
        for (String item : ls) {
            //使用正则表达式判断是否遍历的是数(任意数字均可)!!!
            if (item.matches("\\d+")) {
                //匹配的是多位数
                stack.push(item);
            } else {
                //运算符(从栈中弹出两个数进行运算,再入栈)
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                //开始运算
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("符号错误");
                }
                //将运算后的值入栈
                stack.push(String.valueOf(res));
            }
        }
        return Integer.parseInt(stack.pop());
    }

注:从这里和之前的中缀表达式进行对比,也看得出使用后缀表达式再进行计算非常简单(使用一个栈操作即可)

完整代码可以去我的github上下载:https://github.com/Lzin/stack

在这里插入图片描述

发布了193 篇原创文章 · 获赞 70 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/Lzinner/article/details/102758029