数据结构之使用Stack模拟逆波兰计算器实现

本文详细介绍了中缀表达式转后缀表达式(逆波兰表达式)的过程,包括栈的应用、计算规则,以及Java代码示例。重点讲解了如何通过两个栈操作实现表达式转换,并以'7+((2+3)*6)-(5+(5*2-1))'为例演示计算过程。
摘要由CSDN通过智能技术生成

先简单的说一下栈,Stack栈数据结构遵从先进后出的原则,有着十分广泛的使用,如:

1.java中子程序的调用:

在执行子程序时,会将下个指令的地址存入栈中,子程序执行完毕后再取出地址,回到原来的程序。

2.处理递归调用:

和处理子程序时类似,不过除了下个指令的地址外,也会将局部变量表,操作数栈,动态连接以及方法返回地址一并压入栈中。

3.表达式的转换与求值(如:中缀表达式转后缀表达式并运算)

4.二叉树的遍历

5.图形的深度优先(depth-first)搜索法

必要知识:

关于前缀表达式,中缀表达式和后缀表达式的初步了解

中缀表达式是日常生活中最常见的,也是最容易被人理解的表达式,如:((1+2)*3)/4+5

就是一个中缀表达式,符号在数字的中间

而这种表达式对于计算机来说是不容易理解和处理的,计算器的实现逻辑就是基于后缀表达式来实现计算。

将中缀表达式转为后缀表达式的方法有很多种,这里说一下最简单的:

先看表达式中计算优先级最高的,如((1+2)*3)/4+5 中

优先级最高的是(1+2),将其看成一个整体,去除括号,数字放左边,符号右移得到 1 2 +;

然后将其看成一个整体,继续与*3进行处理

(1 2 + *3)=> 1 2 + 3 *

继续按照这个步骤处理

 1 2 + 3 * /4 => 1 2 + 3 * 4 /

最后一步

1 2 + 3 * 4 / +5 =>  1 2 + 3 * 4 / 5 +

即可得到后缀表达式,也叫逆波兰表达式。

逆波兰表达式的计算规则为,从左至右,拿出两个数字,遇到符号则计算,计算完毕后,用这个新数字去取下一个数字,随后继续计算,直至表达式的最右边。

按照这个规则,1 2 + 3 * 4 / 5 +计算的过程为:
1+2 = 3;

3*3=9;

9/4=2;(java中整形计算自动去除小数)

2+5=7;

模拟计算逆波兰表达式

根据逆波兰表达式的计算逻辑,先来模拟计算逆波兰表达式

 //处理逆波兰表达式,通过集合存储
    public static List<String> getListString(String suffixExpression){
        String[] result = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String ele : result) {
            list.add(ele);
        }
        return list;
    }

这里为了方便计算,将逆波兰表达式通过作为参数传入getListString方法,将其转换为List集合

(对表达式的格式扫描在后面处理,此方法只考虑表达式规范的情况,不校验是否合法)

 //传入list集合计算结果
    public static int calculate(List<String> list){
        //逆波兰表达式计算思路,从左往右计算,如果是数字则入栈,遇到符号则取出数字并计算
        //创建栈
        Stack<String> stack = new Stack<>();
        int num1,num2,res;
        for (String item : list) {
            //如果是数字则入栈,通过正则表达式判断
            if(item.matches("\\d+")){
                stack.push(item);
            }else if(item.equals("+")){
                num2 = Integer.parseInt(stack.pop());
                num1 = Integer.parseInt(stack.pop());
                res = num1+num2;
                stack.push(""+res);
            }else if(item.equals("-")){
                num2 = Integer.parseInt(stack.pop());
                num1 = Integer.parseInt(stack.pop());
                res = num1-num2;
                stack.push(""+res);
            }else if(item.equals("*")){
                num2 = Integer.parseInt(stack.pop());
                num1 = Integer.parseInt(stack.pop());
                res = num1*num2;
                stack.push(""+res);
            }else if(item.equals("/")){
                num2 = Integer.parseInt(stack.pop());
                num1 = Integer.parseInt(stack.pop());
                res = num1/num2;
                stack.push(""+res);
            }else{
                throw new RuntimeException("未知的运算符");
            }
        }
        return Integer.parseInt(stack.pop());
    }

计算的方法如上,核心逻辑需要通过栈处理,num1,num2接收计算的数字,res接收计算结果;

 public static void main(String[] args) {
        String suffixExpression = "30 4 + 5 * 6 -";
        //思路,先将给定的逆波兰表达式处理分割后存入List集合
        List<String> listString = getListString(suffixExpression);
        // System.out.println(listString);
        int calculate = calculate(listString);
        System.out.println(calculate);
    }

在主方法中给定逆波兰表达式: "30 4 + 5 * 6 -"

 计算结果为:164

将表达式改为:"4 5 * 8 - 60 + 8 2 / +"

计算结果为:76

到这里,核心的计算逻辑ok,那么接下来的问题便是如何讲中缀表达式转换为逆波兰表达式

操作流程是:

1.初始化两个栈,运算符号栈s1和中间结果栈s2
2.从左到右扫描中缀表达式
3.遇到操作数时,压入s2
4.遇到运算符时,比较其与 s1栈顶运算符的优先级
        1.如果s1为空,或栈顶运算符号为左括号“(”则直接入s1
        2.如果运算符优先级比s1栈顶运算符优先级高则直接入栈
        3.否则将s1栈顶运算符弹出,并存入s2,再与s1栈顶运算符比较
5.遇到括号时,如果是左括号,直接压入s1,如果是右括号则依次弹出s1栈中数据,并弹入s2,直到遇见左括号,并舍弃这对括号
6.重复上述步骤,直到表达式的最后边
7.将s1中剩余的运算符依次弹出并压入s2
8.依次弹出s2并输出,结果的逆序即为后缀表达式

在执行之前得先补上对表达式的校验,判断表达式是否合法:

 /**
     * 将中缀表达式处理后存入list集合
     * @param infixExpression 传入的中缀表达式
     * @return
     */
    public static List<String> toInfixExpressionList(String infixExpression){
        String newInfix = infixExpression.replaceAll("\\s+","");
        List<String> list =  new ArrayList<>();
        //分情况判断,如果为符号则直接加入集合,如果为数字则判断下一位是否还是数字
        char c;
        String res;
        for (int i = 0; i < newInfix.length(); i++) {
            String item = String.valueOf(newInfix.charAt(i));
            if(item.matches("[+\\-*/()]")){
                list.add(item);
            }else if(item.matches("\\d+")){
                if(i == newInfix.length()-1){
                    list.add(item);
                }else{
                    res = "";
                    while(String.valueOf(c=newInfix.charAt(i)).matches("\\d+")){
                        res+=c;
                        i++;
                    }
                    list.add(res);
                    i--;
                }
            }
        }
        return list;
    }

判断是否为数字时,如果为数字,则判断当前位置是否已经达到了表达式的末尾,如果是则直接加入集合,否则从当前位置开始while循环,直到下一位不是数字,中间使用字符串拼接,循环结束后,将拼接结果加入集合。(由于循环结束前,会再判断一次当前位置是否是数字,如果是符号的话就不执行了,而此时的i实际上是符号,如果不进行i--则会漏掉这个符号)

 public static int priority(String s){
        switch(s){
            case "*":
                return 1;
            case "/":
                return 1;
            case "+":
                return 0;
            case "-":
                return 0;
            default:
                return -1;
        }
    }

 新增一个priority方法判断运算符的优先级

 /**
     * 将中缀表达式转换成后缀表达式
     * @param list 传入的中缀表达式
     * @return
     */
    public static List<String> infixToSuffixExpression(List<String> list){
        List<String> resultList = new ArrayList<>();
        //1.初始化两个栈,运算符号栈s1和中间结果栈s2
        Stack<String>operStack = new Stack<>();
        Stack<String>tempStack = new Stack<>();
        //2.从左到右扫描中缀表达式
        //3.遇到操作数时,压入s2
        //4.遇到运算符时,比较其与 s1栈顶运算符的优先级
        //1.如果s1为空,或栈顶运算符号为左括号“(”则直接入s1
        //2.如果运算符优先级比s1栈顶运算符优先级高则直接入栈
        //3.否则将s1栈顶运算符弹出,并存入s2,再与s1栈顶运算符比较
        //5.遇到括号时,如果是左括号,直接压入s1,如果是右括号则依次弹出s1栈中数据,并弹入s2,直到遇见左括号,并舍弃这对括号
        //6.重复上述步骤,直到表达式的最后边
        for (String s : list) {
            if(s.matches("\\d+")){
                tempStack.push(s);
            }else if(s.matches("[+\\-*/()]")){
                boolean flag = true;
                while(flag){
                    if(operStack.isEmpty()||operStack.peek().equals("(")){
                        operStack.push(s);
                        flag = false;
                    }else if(s.equals(")")){
                        while(true){
                            String next = operStack.pop();
                            if(next.equals("(")){
                                flag = false;
                                break;
                            }
                            tempStack.push(next);
                        }
                    }else if(priority(s)>priority(operStack.peek())){
                        operStack.push(s);
                        flag = false;
                    }else if(s.equals("(")){
                        operStack.push(s);
                        flag = false;
                    }else{
                        tempStack.push(operStack.pop());
                        flag =true;
                    }
                }
            }
        }
        //7.将s1中剩余的运算符依次弹出并压入s2
        while(operStack.size()!=0){
            tempStack.push(operStack.pop());
        }
        //8.依次弹出s2并输出,结果的逆序即为后缀表达式
        while(tempStack.size()!=0){
            resultList.add(tempStack.pop());
        }
        Collections.reverse(resultList);
        return resultList;
    }

转换的核心逻辑

  public static void main(String[] args) {
        //将传入的中缀表达式转换为集合方便计算
        List<String> InfixExpression = toInfixExpressionList("7+((2+3)*6)-(5+(5*2-1))");
        //将中缀表达式转为后缀表达式
        List<String> suffixExpression = infixToSuffixExpression(InfixExpression);
        //计算表达式
        int calculate = calculate(suffixExpression);
        System.out.println(calculate);
    }

测试计算中缀表达式:"7+((2+3)*6)-(5+(5*2-1))"

 计算结果为:23

功能实现完毕!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值