数据结构-栈(三)逆波兰计算器(Java)

1.逆波兰表达式

逆波兰表达式又叫做后缀表达式。逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出的一种表达式的表示方法 [1] 。后来,人们就把用这种表示法写出的表达式称作“逆波兰表达式”。逆波兰表达式把运算量写在前面,把算符写在后面。
逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。例如(a+b)(c+d)转换为ab+cd+

2.逆波兰表达式存在的意义

在我们看来逆波兰表达式阅读起来并不方便,那么它存在的意义是什么呢?

原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。

在上篇博客中,我们根据中缀表达式(即我们平时习惯读的多项式顺序)实现了计算器,但是这种实现方式很难实现有括号的多项式,而逆波兰表达式会完美的解决这个问题,中缀表达式转为逆波兰表达式后就不会存在括号了。

3.中缀表达式转后缀表达式

3.1 实现的思路

  1. 初始化两个栈s1,s2

  2. 从左至右扫描表达式

  3. 遇到数字压入s2

  4. 若遇到运算符,比较与栈顶的优先级
    4.1 若s1为空,或栈顶运算符为左括号 “(”,则将运算符压入s1
    4.2 若优先级比栈顶运算符高,也将运算符压入s1
    4.3 否则,将s1栈顶运算符弹出并压入s2,再转到4.1与s1新的栈顶运算符进行比较

  5. 遇到括号时
    5.1 如果为"(",直接压入s1
    5.2 如果为")",则依次弹出s1栈顶运算符,压入s2,直到遇到左括号,此时将这一对括号“丢弃”

  6. 重复2-5步骤直到表达式最右边

  7. 将s1中剩余的运算符依次弹出并压入s2

  8. 依次弹出s2中的元素并输出,将结果逆序,即为后缀表达式
    算法图示:
    在这里插入图片描述

在整个思路中我们发现s2仅仅是进行压栈的操作,并且因为s2栈的特点,最后输出时需要对结果进行逆序输出,所以我们可以使用ArrayList充当s2的数据结构,这样我们就不需要最后进行逆序操作,直接输出即是后缀表达式

4.如何计算后缀表达式

当中缀表达式转为后缀表达式后,我们不用考虑优先级的问题,因为后缀表达式的顺序就是计算顺序,我们只需要按照规则进行计算即可
9. 对后缀表达式从左至右开始扫描
10. 如果遇到数字,进行压栈操作
11. 如果遇到操作符,弹出栈顶两个数(次栈顶在前),用该计算符进行计算,将结果压栈,直到最后。

5.代码实现

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 第一步,首先完成了后缀表达式的计算器
 * 第二步,需要完成中缀表达式转后缀表达式的功能4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 6 + 8 2 / +
 */
public class PolandNotation {
    public static void main(String[] args) {
        //先定义一个逆波兰表达式
        //(30+4)*5-6
        // 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 6 + 8 2 / +
        String expression = "1+((2+3)*4)-5";
        List<String> infixExpressionList = toInfixExpressionList(expression);
        System.out.println("中缀表达式对应List"+ infixExpressionList);
        List<String> suffixExpresionList = parseSuffixExpresionList(infixExpressionList);
        System.out.println("后缀表达式对应List:"+ suffixExpresionList);
        System.out.printf("Expression=%d", calculate(suffixExpresionList));
//        //1.先将suffixExpression放到ArrayList中
//        //2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算器
//        List<String> rpnList = getListString(suffixExpression);
//        System.out.println("rpnList" + rpnList);
//
//        int res = calculate(rpnList);
//        System.out.println("计算的结果是:" + res);
    }


    public static List<String> parseSuffixExpresionList(List<String> list){
        //定义两个栈
        Stack<String> s1 = new Stack<>(); //符号栈
        //因为s2在整个转换过程中没有pop操作,而且还需要逆序输出
        //因此我们这里直接使用list进行输出
        List<String> s2 = new ArrayList<String>();
        //遍历list
        for (String item: list) {
            //如果是一个数,就入s2
            if(item.matches("\\d+")){
                s2.add(item);
            }else if(item.equals("(")){
                s1.push(item);
            }else if(item.equals(")")) {
                //如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,知道遇到左括号为止,此时将这一对括号丢弃
                while (!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();//将小括号弹出,消除小括号
            }else {
                //考虑到运算符优先级的问题
                //当item的优先级小于或者等于栈顶运算符,弹出s1加入s2,再继续用item与新的栈顶元素作比较
                //缺少比较优先级高低的方法
                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());
        }
        return s2;//此时的就是对应的逆波兰

    }


    /**
     * 将中缀表达式转为后缀表达式的list
     * @param s
     * @return
     */
    public static List<String> toInfixExpressionList(String s){
        //定义一个list,放中缀表达式
        List<String> list = new ArrayList<String>();
        int i = 0; //这是一个指针,用于遍历中缀表达式字符串
        String str;//对多位数的拼接
        char c;//每遍历一个字符就放到c
        do{
            //如果c是一个非数字,就需要加入到ls中
            if((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
                list.add("" + c);
                i++;//i需要后移
            }else{ //如果是一个数,需要考虑多位数
                str = "";//置空串
                while (i < s.length() && (c=s.charAt(i)) >= 48 && (c=s.charAt(i)) <= 57){ //对整数的处理
                    str += c;//拼接
                    i++;
                }
                list.add(str);
            }
        }while (i < s.length());
        return list;
    }


    //将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        //分割字符串
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<String>();
        for (String ele: split) {
            list.add(ele); //依次放入字符
        }
        return list;
    }

    //完成逆波兰表达式的运算
    public static int calculate(List<String> ls){
        //创建一个栈。只需要一个栈
        Stack<String> stack = new Stack<>();
        //遍历
        for (String item:ls) {
            //这里使用正则表达式取出来数
            if(item.matches("\\d+")){//匹配多位数
                stack.push(item);
            }else {
                //pop两个数运算再入栈
                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(res + "");
            }
        }
        //最后留在stack的数据就是结果
        return Integer.parseInt(stack.pop());
    }
}

//增加一个类Operation 返回运算符对应的优先级
class Operation{
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;
    //写一个方法,返回对应的优先级数字
    public static int getValue(String operation){
        int result = 0;
        switch (operation){
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
                default:
                    break;
        }
        return result;
    }

结果:

在这里插入图片描述

欢迎访问我的个人博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值