【栈】逆波兰计算器

2023-11-15

文章目录

前言

请输入一个表达式:计算式:[7*2*2-5+1-5+3-3]
在这里插入图片描述
请问: 计算机底层是如何运算得到结果的?
注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5, 但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题

一:基本概念

1.1 介绍

  1. 栈的英文为(stack),栈是一个先入后出(FILO-First In Last Out)的有序列表。
  2. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
  3. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

1.2 入栈和出栈示意图

在这里插入图片描述
在这里插入图片描述

1.3 栈的应用场景

  1. 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  2. 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  3. 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
  4. 二叉树的遍历。
  5. 图形的深度优先(depth一first)搜索法。

二:使用数组模拟栈

2.1 思路分析

在这里插入图片描述

  1. 使用数组来模拟栈
  2. 定义一个 top 来表示栈顶,初始化 为 -1
  3. 入栈的操作,当有数据加入到栈时, top++; stack[top] = data;
  4. 出栈的操作, int value = stack[top]; top–, return value

2.2 代码实现

class ArrayStack {

    /**
     * 栈的大小
     */
    private int maxSize;

    /**
     * 存放栈的数据
     * 数组模拟栈
     */
    private int[] stack;

    /**
     * top表示栈顶,初始化为-1
     */
    private int top = -1;


    /**
     * 有参构造
     *
     * @param maxSize
     */
    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        //初始化栈
        stack = new int[maxSize];
    }

    /**
     * 判断栈是否满了
     *
     * @return boolean
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }

    /**
     * 判断栈是否为空
     *
     * @return boolean
     */
    public boolean isEmpty() {
        return top == -1;
    }

    /**
     * 入栈
     *
     * @param value
     */
    public void push(int value) {
        //先判断栈是否满了
        if (isFull()) {
            System.out.println("栈满了");
            return;
        }
        top++;
        stack[top] = value;
    }

    /**
     * 出栈-将栈顶的数据返回
     *
     * @return int
     */
    public int pop() {
        //先判断栈是否为空
        if (isEmpty()) {
            throw new RuntimeException("栈是空的");
        }
        //取出栈顶的值
        int value = stack[top];
        top--;
        return value;
    }

    /**
     * 遍历栈
     * 遍历时,需要从栈顶显示数据
     */
    public void list() {
        //先判断栈是否为空
        if (isEmpty()) {
            System.out.println("栈是空的,无法遍历");
            return;
        }
        //从栈顶开始遍历
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }

    }


}

2.3 测试

public class ArrayStackDemo {
    public static void main(String[] args) {
        //测试一下
        //先创建一个ArrayStack对象
        ArrayStack stack = new ArrayStack(4);
        String key = "";
        //控制是否退出菜单
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);
        while (loop) {
            System.out.println("show: 表示显示栈");
            System.out.println("exit: 退出程序");
            System.out.println("push: 表示添加数据到栈(入栈)");
            System.out.println("pop: 表示从栈取出数据(出栈");
            System.out.println("请输入你的选择");
            key = scanner.next();
            switch (key) {
                case "show":
                    stack.list();
                    break;
                case "push":
                    System.out.println("请输入一个数");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        System.out.printf("出栈的数据是 %dn", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;

            }
        }
        System.out.println("程序退出");
    }
}

三:使用栈模拟中缀表达式计算器

3.1 整体思路

创建两个栈,一个是数栈专门用来存放数字,另一个是符号栈用来存放符号。
在这里插入图片描述

  1. 使用一个index值(索引),来遍历我们的表达式
  2. 如果我们发现是一个数字,那么就入数栈
  3. 如果发现是一个字符,那么就分如下两种情况
    1)如果当前符号栈为空,那么就直接入栈
    2)如果当前符号栈有操作符,就进行比较。如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop出一个符号,进行运算,将得到的结果,入数栈。然后将当前操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,则直接入符号栈。
  4. 当表达式扫描完毕,就顺序从数栈和符号栈中pop出相应的数和符号,并运行。
  5. 最后在数栈中只有一个数字,就是表达式的结果

3.2 验证3+2*6-2=13

3.2.1 定义栈

class ArrayStackCalculator {

    /**
     * 栈的大小
     */
    private int maxSize;

    /**
     * 存放栈的数据
     * 数组模拟栈
     */
    private int[] stack;

    /**
     * top表示栈顶,初始化为-1
     */
    private int top = -1;


    /**
     * 有参构造
     *
     * @param maxSize
     */
    public ArrayStackCalculator(int maxSize) {
        this.maxSize = maxSize;
        //初始化栈
        stack = new int[maxSize];
    }

    /**
     * 判断栈是否满了
     *
     * @return boolean
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }

    /**
     * 判断栈是否为空
     *
     * @return boolean
     */
    public boolean isEmpty() {
        return top == -1;
    }

    /**
     * 入栈
     *
     * @param value
     */
    public void push(int value) {
        //先判断栈是否满了
        if (isFull()) {
            System.out.println("栈满了");
            return;
        }
        top++;
        stack[top] = value;
    }

    /**
     * 出栈-将栈顶的数据返回
     *
     * @return int
     */
    public int pop() {
        //先判断栈是否为空
        if (isEmpty()) {
            throw new RuntimeException("栈是空的");
        }
        //取出栈顶的值
        int value = stack[top];
        top--;
        return value;
    }

    /**
     * 遍历栈
     * 遍历时,需要从栈顶显示数据
     */
    public void list() {
        //先判断栈是否为空
        if (isEmpty()) {
            System.out.println("栈是空的,无法遍历");
            return;
        }
        //从栈顶开始遍历
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }

    }


}

3.2.2 返回运算符的优先级

/**
     * 返回运算符的优先级,优先级使用数字表示
     * 数字越大,优先级越高
     * <p>
     * 假设目前表达式只有 + - * /
     *
     * @return
     */
    public int priority(int str) {
        if (str == '*' || str == '/') {
            return 1;
        } else if (str == '+' || str == '-') {
            return 0;
        } else {
            return -1;
        }
    }

3.2.3 判断是不是一个运算符

    /**
     * 判断是不是一个运算符
     *
     * @param val
     * @return
     */
    public boolean isOpera(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

3.2.4 计算方法

/**
     * 计算方法
     *
     * @param num1
     * @param num2
     * @param opera
     * @return
     */
    public int cal(int num1, int num2, char opera) {
        //用于存放计算的结果
        int res = 0;
        switch (opera) {
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }

3.2.5 查看栈顶的值

/**
     * 查看栈顶的值,并进行返回
     *
     * @return
     */
    public int peek() {
        return stack[top];
    }

3.2.6 存在问题及解决思路

存在问题:
当表达式存在两位数时,入数栈的时候会有问题,例如30+2*8-2。所以如果为数字,则直接入数栈的时候,我们需要优化一下思路。
解决思路:
1.当处理数字入栈时,不能发现是一个数就直接入栈,因为有可能是多位数
2.在处理数字时,需要向expression的表达式的index后再看一位,如果是数字则继续扫描,如果是符号才入栈
3.因此我们需要定义一个变量字符串,用于拼接

3.2.7 核心代码编写

public static void main(String[] args) {
        //需要计算的表达式
        String expression = "3+2*8-2";
        //创建两个栈,一个是数栈,一个是符号栈
        ArrayStackCalculator numStack = new ArrayStackCalculator(20);
        ArrayStackCalculator operaStack = new ArrayStackCalculator(20);
        //定义需要扫描的变量
        int index = 0;
        int num1 = 0;
        int num2 = 0;
        char opera;
        int res = 0;
        //用于拼接多位数
        String keepNum = "";
        //将每次扫描出来的char保存到ch中
        char ch = ' ';
        //使用while语句开始扫描expression
        //此时说明已经扫描到最后一个节点,跳出while循环
        while (index < expression.length()) {
            //依次得到expression的每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            //判断ch是什么,就做什么处理
            if (operaStack.isOpera(ch)) {
                //如果为运算符
                //判断当前符号栈是否为空
                if (!operaStack.isEmpty()) {
                    //如果当前符号栈有操作符,就进行比较。如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,
                    //再从符号栈中pop出一个符号进行运算,将得到的结果,入数栈。然后将当前操作符入符号栈,
                    if (operaStack.priority(ch) <= operaStack.priority(operaStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        opera = (char) operaStack.pop();
                        res = numStack.cal(num1, num2, opera);
                        //将计算的结果入数栈
                        numStack.push(res);
                        //将当前操作符入符号栈
                        operaStack.push(ch);
                    } else {
                        //如果当前的操作符的优先级大于栈中的操作符,则直接入符号栈
                        operaStack.push(ch);
                    }
                } else {
                    //如果为空直接入栈
                    operaStack.push(ch);
                }
            } else {
                //如果为数字,则直接入数栈
                //将字符转化为数字,需要-48
                //numStack.push(ch - 48);
                //存在问题:
                //当表达式存在两位数时,入数栈的时候会有问题,例如`30+2*8-2`。所以如果为数字,则直接入数栈的时候,我们需要优化一下思路。
                //解决思路:
                //1.当处理数字入栈时,不能发现是一个数就直接入栈,因为有可能是多位数
                //2.在处理数字时,需要向expression的表达式的index后再看一位,如果是数字则继续扫描,如果是符号才入栈
                //3.因此我们需要定义一个变量字符串,用于拼接
                keepNum += ch;
                //ch如果已经是expression的最后一位,则不要判断下一位,直接入栈
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                } else {
                    //判断下一个字符是不是数字,如果是数字,则继续扫描,如果是符号则直接入栈
                    //下一位不是index++,index值不要变
                    if (operaStack.isOpera(expression.substring(index + 1, index + 2).charAt(0))) {
                        //如果后一位是操作符,则入栈
                        numStack.push(Integer.parseInt(keepNum));
                        //此时注意一定要清空keepNum
                        keepNum = "";
                    }
                }
            }
            //让index加一,并且判断是否扫描到expression的最后一位
            index++;
        }
        //当表达式扫描完毕,就顺序从数栈和符号栈中pop出相应的数和符号,并运行
        while (!operaStack.isEmpty()) {
            //如果符号栈为空,则计算到最后的结果,数栈中只有一个数字【结果】
            num1 = numStack.pop();
            num2 = numStack.pop();
            opera = (char) operaStack.pop();
            res = numStack.cal(num1, num2, opera);
            numStack.push(res);
        }
        //将数栈最后的数pop出来
        int result = numStack.pop();
        System.out.printf("表达式%s=%d", expression, result);

    }

四:前缀、中缀、后缀表达式的介绍

4.1 前缀表达式(波兰表达式)

4.1.1 概念

前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6

4.2.2 前缀表达式的计算机求值

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和
次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:

  1. 从右至左扫描,将6、5、4、3压入堆栈
  2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
  3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
  4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

4.2 中缀表达式

  1. 中缀表达式就是常见的运算表达式,如(3+4)×5-6
  2. 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)

4.3 后缀表达式(逆波兰表达式)

4.3.1 概念

  1. 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
  2. 中举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
  3. 再比如:
    在这里插入图片描述

4.3.2 后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和
栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:

  1. 从左至右扫描,将3和4压入堆栈;
  2. 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
  3. 将5入栈;
  4. 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
  5. 将6入栈;
  6. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

五:后缀表达式(逆波兰计算器)

原表达式:(3+4)×5-6
后缀表达式:3 4 + 5 × 6 -

5.1 思路分析

  1. 先将3 4 + 5 × 6 -放入到arrayList中
  2. 将arrayList传递给一个方法,遍历arrayList,配合栈完成计算

5.2 代码实现

/**
 * 逆波兰计算器
 *
 * @author ikun
 */
public class PolandNotation {
    public static void main(String[] args) {
        //先定义一个逆波兰表达式
        //(3+4)×5-6  ->  3 4 + 5 × 6 -
        //数字和符号之间空格隔开
        String suffixExpression = "3 4 + 5 * 6 - ";
        //思路
        //1.先将3 4 + 5 × 6 -放入到arrayList中
        //2.将arrayList传递给一个方法,遍历arrayList,配合栈完成计算
        List<String> list = getListString(suffixExpression);
        int res = calculate(list);
        System.out.printf("计算的结为%d", res);
    }

    /**
     * 将一个逆波兰表达式,依次将数据和运算符放入到arrayList中
     *
     * @param suffixExpression 逆波兰表达式
     * @return List<String>
     */
    public static List<String> getListString(String suffixExpression) {
        List<String> list = new ArrayList<>();
        char[] charArray = suffixExpression.toCharArray();
        for (char c : charArray) {
            if (c != ' ') {
                list.add(String.valueOf(c));
            }
        }
        return list;
    }

    /**
     * 完成逆波兰表达式的运算
     * 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
     * <p>
     * 1.从左至右扫描,将3和4压入堆栈;
     * 2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
     * 3.将5入栈;
     * 4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
     * 5.将6入栈;
     * 6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     * </p>
     *
     * @return int value
     */
    public static int calculate(List<String> list) {
        //创建栈,只需一个即可
        Stack<String> stack = new Stack<>();
        //遍历list
        for (String item : list) {
            //这里使用正则表达式来取数
            //匹配的是多位数
            if (item.matches("\\d+")) {
                //入栈
                stack.push(item);
            } else {
                //pop出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch (item) {
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num1 - num2;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                //把res入栈
                stack.push(String.valueOf(res));
            }

        }
        //最后stack中的数据就是运算符的结果
        return Integer.parseInt(stack.pop());
    }


}

六:中缀表达式转后缀表达式

大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。

6.1 思路分析

  1. 初始化两个栈:运算符栈s1和存储中间结果的栈s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压s2;
  4. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
  5. 遇到括号时:
    1)如果是左括号“(”,则直接压入s1;
    2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤2至5,直到表达式的最右边
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

6.2 举例说明

将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式"1 2 3 + 4 × + 5 –"

在这里插入图片描述

6.3 代码实现

6.3.1 将一个中缀表达式,依次将数据和运算符放入到arrayList中

/**
     * 将一个中缀表达式,依次将数据和运算符放入到arrayList中
     * expression -> 1+((2+3)*4)-5
     *
     * @param expression 中缀表达式
     * @return List<String>
     */
    public static List<String> toInfixExpressionList(String expression) {
        List<String> list = new ArrayList<>();
        //index表示指针,用于遍历中缀表达式字符串
        int index = 0;
        //对多位数进行拼接
        String str;
        //每遍历一个字符就放到ch中
        char ch;
        do {
            if ((ch = expression.charAt(index)) < 48 || (ch = expression.charAt(index)) > 57) {
                //如果ch是一个字符,则需要添加到list中
                list.add(String.valueOf(ch));
                //index需要向后移
                index++;
            } else {
                str = "";
                //如果是数字,则需要考虑多位数的情况
                while (index < expression.length() && (ch = expression.charAt(index)) >= 48 && (ch = expression.charAt(index)) <= 57) {
                    //拼接字符
                    str += ch;
                    index++;
                }
                list.add(str);
            }

        } while (index < expression.length());
        return list;

    }

6.3.2 返回一个运算符对应的优先级

/**
 * 返回一个运算符对应的优先级
 */
class Operation {

    private static final int ADD = 1;

    private static final int SUB = 1;

    private static final int MUL = 2;

    private static final int DIV = 2;

    /**
     * 返回对应优先级的数字
     *
     * @param operation
     * @return
     */
    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:
                System.out.println("该运算符不存在");
                break;
        }
        return result;

    }

}

6.3.3 将中缀表达式转化为后缀表达式

/**
     * 将中缀表达式转化为后缀表达式
     *
     * @param infixExpressionList
     * @return
     */
    public static List<String> parseSuffixExpressionList(List<String> infixExpressionList) {
        //符号栈
        Stack<String> charStack = new Stack<>();
        //中间结果栈只有入栈不会出栈,而且需要逆序输出,所以用arrayList代替
        List<String> resultList = new ArrayList<>();
        //遍历infixExpressionList
        for (String item : infixExpressionList) {
            //如果是一个数就加入到结果栈resultList
            if (item.matches("\\d+")) {
                resultList.add(item);
            } else if (item.equals("(")) {
                charStack.add(item);
            } else if (item.equals(")")) {
                //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
                while (!charStack.peek().equals("(")) {
                    String pop = charStack.pop();
                    resultList.add(pop);
                }
                //此时丢弃charStack里面的“(”
                charStack.pop();
            } else {
                //若item的优先级小于等于符号栈栈顶运算符,则将符号栈顶的运算符弹出并压入到结果栈中
                while (charStack.size() > 0 && Operation.getValue(charStack.peek()) >= Operation.getValue(item)) {
                    String pop = charStack.pop();
                    resultList.add(pop);
                }
                //将item压入到charStack
                charStack.push(item);
            }
        }
        //将符号栈剩余的符号加入到结果栈
        while (charStack.size() > 0) {
            String pop = charStack.pop();
            resultList.add(pop);
        }
        //因为是list所以按顺序输出就是对应的波兰表达式
        return resultList;
    }

6.3.4 完成逆波兰表达式的运算

/**
     * 完成逆波兰表达式的运算
     * 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
     * <p>
     * 1.从左至右扫描,将3和4压入堆栈;
     * 2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
     * 3.将5入栈;
     * 4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
     * 5.将6入栈;
     * 6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     * </p>
     *
     * @return int value
     */
    public static int calculate(List<String> list) {
        //创建栈,只需一个即可
        Stack<String> stack = new Stack<>();
        //遍历list
        for (String item : list) {
            //这里使用正则表达式来取数
            //匹配的是多位数
            if (item.matches("\\d+")) {
                //入栈
                stack.push(item);
            } else {
                //pop出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch (item) {
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num1 - num2;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                //把res入栈
                stack.push(String.valueOf(res));
            }

        }
        //最后stack中的数据就是运算符的结果
        return Integer.parseInt(stack.pop());
    }


}

6.3.5 完整代码奉上

package com.sysg.dataStructuresAndAlgorithms.stack;

import java.util.*;


/**
 * 逆波兰计算器
 *
 * @author ikun
 */
public class PolandNotation {

    public static void main(String[] args) {
        //说明
        //1.将中缀表达式转化为后缀表达式
        //2.将1+((2+3)*4)-5  ->  1 2 3 + 4 * + 5 –
        //3.首先将1+((2+3)×4)-5转化为对应的list,即['1','+','(','(','2','+','3',')','*','4',')','-','5']
        String expression = "1+((2+3)*4)-5";
        List<String> infixExpressionList = toInfixExpressionList(expression);
        System.out.println("中缀表达式:" + infixExpressionList);
        //4.将中缀表达式转化为后缀表达式
        List<String> suffixStringList = parseSuffixExpressionList(infixExpressionList);
        System.out.println("后缀表达式:" + suffixStringList);
        //数字和符号之间空格隔开
        //String suffixExpression = "3 4 + 5 * 6 -";
        //思路
        //1.先将3 4 + 5 * 6 -放入到arrayList中
        //2.将arrayList传递给一个方法,遍历arrayList,配合栈完成计算
        //List<String> suffixStringList = getListSuffixString(suffixExpression);
        int res = calculate(suffixStringList);
        System.out.printf("计算的结为%d", res);
    }

    /**
     * 将中缀表达式转化为后缀表达式
     *
     * @param infixExpressionList
     * @return
     */
    public static List<String> parseSuffixExpressionList(List<String> infixExpressionList) {
        //符号栈
        Stack<String> charStack = new Stack<>();
        //中间结果栈只有入栈不会出栈,而且需要逆序输出,所以用arrayList代替
        List<String> resultList = new ArrayList<>();
        //遍历infixExpressionList
        for (String item : infixExpressionList) {
            //如果是一个数就加入到结果栈resultList
            if (item.matches("\\d+")) {
                resultList.add(item);
            } else if (item.equals("(")) {
                charStack.add(item);
            } else if (item.equals(")")) {
                //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
                while (!charStack.peek().equals("(")) {
                    String pop = charStack.pop();
                    resultList.add(pop);
                }
                //此时丢弃charStack里面的“(”
                charStack.pop();
            } else {
                //若item的优先级小于等于符号栈栈顶运算符,则将符号栈顶的运算符弹出并压入到结果栈中
                while (charStack.size() > 0 && Operation.getValue(charStack.peek()) >= Operation.getValue(item)) {
                    String pop = charStack.pop();
                    resultList.add(pop);
                }
                //将item压入到charStack
                charStack.push(item);
            }
        }
        //将符号栈剩余的符号加入到结果栈
        while (charStack.size() > 0) {
            String pop = charStack.pop();
            resultList.add(pop);
        }
        //因为是list所以按顺序输出就是对应的波兰表达式
        return resultList;
    }


    /**
     * 将一个中缀表达式,依次将数据和运算符放入到arrayList中
     * expression -> 1+((2+3)*4)-5
     *
     * @param expression 中缀表达式
     * @return List<String>
     */
    public static List<String> toInfixExpressionList(String expression) {
        List<String> list = new ArrayList<>();
        //index表示指针,用于遍历中缀表达式字符串
        int index = 0;
        //对多位数进行拼接
        String str;
        //每遍历一个字符就放到ch中
        char ch;
        do {
            if ((ch = expression.charAt(index)) < 48 || (ch = expression.charAt(index)) > 57) {
                //如果ch是一个字符,则需要添加到list中
                list.add(String.valueOf(ch));
                //index需要向后移
                index++;
            } else {
                str = "";
                //如果是数字,则需要考虑多位数的情况
                while (index < expression.length() && (ch = expression.charAt(index)) >= 48 && (ch = expression.charAt(index)) <= 57) {
                    //拼接字符
                    str += ch;
                    index++;
                }
                list.add(str);
            }

        } while (index < expression.length());
        return list;

    }

    /**
     * 将一个后缀表达式,依次将数据和运算符放入到arrayList中
     *
     * @param suffixExpression 逆波兰表达式
     * @return List<String>
     */
    public static List<String> getListSuffixString(String suffixExpression) {
        List<String> list = null;
        String[] str = suffixExpression.split(" ");
        list = Arrays.asList(str);
        return list;
    }

    /**
     * 完成逆波兰表达式的运算
     * 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
     * <p>
     * 1.从左至右扫描,将3和4压入堆栈;
     * 2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
     * 3.将5入栈;
     * 4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
     * 5.将6入栈;
     * 6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     * </p>
     *
     * @return int value
     */
    public static int calculate(List<String> list) {
        //创建栈,只需一个即可
        Stack<String> stack = new Stack<>();
        //遍历list
        for (String item : list) {
            //这里使用正则表达式来取数
            //匹配的是多位数
            if (item.matches("\\d+")) {
                //入栈
                stack.push(item);
            } else {
                //pop出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch (item) {
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num1 - num2;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                //把res入栈
                stack.push(String.valueOf(res));
            }

        }
        //最后stack中的数据就是运算符的结果
        return Integer.parseInt(stack.pop());
    }


}

/**
 * 返回一个运算符对应的优先级
 */
class Operation {

    private static final int ADD = 1;

    private static final int SUB = 1;

    private static final int MUL = 2;

    private static final int DIV = 2;

    /**
     * 返回对应优先级的数字
     *
     * @param operation
     * @return
     */
    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:
                System.out.println("该运算符不存在");
                break;
        }
        return result;

    }

}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【栈】逆波兰计算器 的相关文章

  • error 1962

    今天电脑出了一点小毛病 报错 error 1962 No operation system found 由于很多的资料还有项目在里边存着 而且只有一个c盘 这可怎么解决 对于一个英文不好的中国人来说 这可是个麻烦事 静下心 仔细琢磨还是能够
  • C++11新特性

    1 列表初始化 2 变量类型推导 3 范围for循环 4 final与override 5 智能指针 6 新增加容器 静态数组array forward list以及unordered系列 7 默认成员函数控制 8 右值引用 9 lambd

随机推荐

  • gcc -lpthread

    转自 http www cnblogs com suntp p 6473751 html 如果用gcc编译使用了POSIX thread的程序时 通常需要加额外的选项 以便使用thread safe的库及头文件 一些老的书里说直接增加链接选
  • 《机器学习的随机矩阵方法》

    机器学习的随机矩阵方法 作者 Romain Couillet 和Zhenyu Liao 出版商 剑桥大学出版社 第 1 版 2022 年 10 月 31 日 语言 英语 精装版 408 页 ISBN 10 1009123238 ISBN 1
  • chrome浏览器https证书不安全页面打开设置

    访问https协议的页面浏览器都会加载此页面所需要的证书 在证书不被信任 即证书不是有正规CA机构颁发的话 通常是由自己通过证书生成工具或命令生成的 chrome浏览器会提示页面不安全而不会直接访问该页面 此时有两种选择 选择安全方式 不再
  • 面试题创作0005,请说明Linux 和 AI的关系(联系和区别)

    请说明Linux 和 AI的关系 联系和区别 可以在AI的业务应用 平台服务提供 平台设备商 集成电路开发等各个跟AI相关的行业来寻找联系和区别
  • MATLAB——矩阵与阵列

    变量及操作 变量命名规则 赋值语句 运算符和表达式 矩阵产生与表示 直接输入法创建矩阵 向量法创建矩阵 函数法创建矩阵 特殊矩阵 矩阵元素的引用 矩阵单个元素与行列提取 向量标识方式 矩阵基本操作 矩阵提取子块 合并短阵 转置与展开 提取子
  • Lion闭源大语言模型的对抗蒸馏框架实践

    Lion闭源大语言模型的对抗蒸馏框架实践 概述 对抗蒸馏框架概述 我们基于高级闭源LLM的基础上提炼一个学生LLM 该LLM具有三个角色 教师 裁判和生成器 有三个迭代阶段 模仿阶段 对于一组指令 将学生的响应与老师的响应对齐 区分阶段 识
  • ESP8266-NodeMCU(一)

    ESP8266 NodeMCU开发板的驱动有CH340和CP210等等 本文使用ESP8266 NodeMCU CH340驱动版本 一 开发板详解 NodeMCU是一个开源的IoT物联网硬件开发板 由于它支持WIFI功能且使用方法十分类似A
  • RAM处理器的8种寻址方式

    什么是寻址 寻址是指找到存储数据或指令的地址 然后读取其中的内容 寻址方式就是处理器根据指令中给出的地址信息来寻找有效地址的方式 是确定本条指令的数据地址以及下一条要执行的指令地址的方法 ARM处理器采用的RISC架构 CPU本身是不能直接
  • MyBatis resultMap collection标签 返回基本类型集合 如:List<Long> List<String> List<Integer>等

    class xxDTO private Long id private Set
  • vc 判断某个盘符是否为移动硬盘盘符

    在使用GetDriveType获取磁盘类型时 一般小容量的U盘直接返回DRIVE REMOVABLE 倒是不用再进行下一步的判断 而大容量U盘和移动硬盘的盘符返回值和本地硬盘盘符返回值都是DRIVE FIXED 需要再进行判断 如果是IDE
  • 【paddlepaddle】一键人物抠图

    效果 环境准备 win11 python3 8 pip install paddlepaddle i https pypi tuna tsinghua edu cn simple pip install paddlehub i https
  • animation 动画的定义和使用

    keyframes 定义动画 keyframes myname 0 50 100 调用动画 div animation name myfirst animation duration 5s animation timing function
  • Unity 开发人员转CGE(castle Game engine)城堡游戏引擎指导手册

    Unity 开发人员的城堡游戏引擎概述 一 简介 2 Unity相当于什么GameObject 3 如何设计一个由多种资产 生物等组成的关卡 4 在哪里放置特定角色的代码 例如生物 物品 Unity 中 向 GameObject 添加 Mo
  • U盘启动盘制作(步骤详细)

    U盘启动盘制作 在制作启动盘之前我们需要先准备一个8G以上的U盘 和一台能上网的电脑 一 下载系统镜像 根据自己需要的系统版本去下载官方的镜像文件 记得要下载纯净的镜像 否则在后续安装好系统后会出现捆绑的现象 可以直接去下面这个网站下载 下
  • rsync实现文件服务器间文件同步

    rsync介绍 rsync命令工具可以实现服务器间的文件同步 全量或者增量 比如使用 size only来检查源端文件和目标端文件大小是否一致决定是否需要同步 由此同步的功能扩展 可以实现本机不同目录文件拷贝 快速删除海量文件等功能 但要注
  • MySQL隔离级别

    表结构和表数据如下 id 自增主键 uid 唯一索引 name price 普通索引 pictures 33 a Apple 12 NULL 34 b banana 5 NULL 35 c cherry 51 NULL 36 d date
  • Python语言 :关于使用装饰器的技巧介绍

    转自 微点阅读 https www weidianyuedu com 导语 装饰器 Decorator 是 Python 里的一种特殊工具 它为我们提供了一种在函数外部修改函数的灵活能力 它有点像一顶画着独一无二 符号的神奇帽子 只要将它戴
  • 抽象,内部类,接口,多态

    final 最终的 不能改变的 单独应用几率低 修饰变量 变量不能被改变 修饰方法 方法不能被重写 修饰类 类不能被继承 static final常量 应用率高 必须声明同时初始化 常常通过类名点来访问 不能被改变 建议 常量名所有字母都大
  • android — NDK生成so文件

    我们在安装环境的时候安装了NDK 可以在eclipse下直接生成so文件 NDK的压缩包里面自带了一些sample工程 NDK的文件直接解压到某个目录下即可 第一次生成so文件的时候 我们先使用NDK的sample下的hello jni的例
  • 【栈】逆波兰计算器

    文章目录 前言 一 基本概念 1 1 介绍 1 2 入栈和出栈示意图 1 3 栈的应用场景 二 使用数组模拟栈 2 1 思路分析 2 2 代码实现 2 3 测试 三 使用栈模拟中缀表达式计算器 3 1 整体思路 3 2 验证3 2 6 2