【数据结构与算法】栈

news2025/4/12 16:19:32

文章目录

  • 前言
  • 一:基本概念
    • 1.1 介绍
    • 1.2 入栈和出栈示意图
    • 1.3 栈的应用场景
  • 二:使用数组模拟栈
    • 2.1 思路分析
    • 2.2 代码实现
    • 2.3 测试
  • 三:使用栈模拟中缀表达式计算器
    • 3.1 整体思路
    • 3.2 验证3+2*6-2=13
      • 3.2.1 定义栈
      • 3.2.2 返回运算符的优先级
      • 3.2.3 判断是不是一个运算符
      • 3.2.4 计算方法
      • 3.2.5 查看栈顶的值
      • 3.2.6 存在问题及解决思路
      • 3.2.7 核心代码编写
  • 四:前缀、中缀、后缀表达式的介绍
    • 4.1 前缀表达式(波兰表达式)
      • 4.1.1 概念
      • 4.2.2 前缀表达式的计算机求值
    • 4.2 中缀表达式
    • 4.3 后缀表达式(逆波兰表达式)
      • 4.3.1 概念
      • 4.3.2 后缀表达式的计算机求值
  • 五:后缀表达式(逆波兰计算器)
    • 5.1 思路分析
    • 5.2 代码实现
  • 六:中缀表达式转后缀表达式
    • 6.1 思路分析
    • 6.2 举例说明
    • 6.3 代码实现
      • 6.3.1 将一个中缀表达式,依次将数据和运算符放入到arrayList中
      • 6.3.2 返回一个运算符对应的优先级
      • 6.3.3 将中缀表达式转化为后缀表达式
      • 6.3.4 完成逆波兰表达式的运算
      • 6.3.5 完整代码奉上

前言

请输入一个表达式:计算式:[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;

    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/975908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何通过Instagram群发消息高效拓展客户?

之前小S有跟大家说过关于独立站&#xff0b;Instagram如何高效引流&#xff0c;发现大家都对Instagram的话题挺关注的。Instagram作为全球最受欢迎的社交媒体之一&#xff0c;对于许多商家和营销人员来说&#xff0c;Instagram是一个不可忽视的营销平台&#xff0c;他们可以通过…

痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU硬件那些事(2.3)- 串行NOR Flash下载算法(J-Link工具篇)

https://www.cnblogs.com/henjay724/p/13770137.html 大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家介绍的是J-Link工具下i.MXRT的串行NOR Flash下载算法设计。 在i.MXRT硬件那些事系列之《在串行NOR Flash XIP调试原理》一文中&#xff0c;痞…

通过rabbitmq生成延时消息,并生成rabbitmq镜像

通过rabbitmq生成延时消息队列&#xff0c;并生成rabbitmq镜像 整体描述1. 使用场景2. 目前问题3. 前期准备 具体步骤1. 拉取镜像2. 运行镜像3. 安装插件4. 代码支持4.1 config文件4.2 消费监听4.2 消息生产 5. 功能测试 镜像操作1. 镜像制作2. 镜像导入 总结 整体描述 1. 使用…

MySql学习笔记08——事务介绍

事务 基本概念 事务是一个完整的业务逻辑&#xff0c;是一个最小的工作单元&#xff0c;不可再分。 一个完整的业务逻辑包括一系列的操作&#xff0c;这些操作是整个业务逻辑中的最小单元&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败。 由于只有DML语句中才会…

C++那些事之Step by step上手grpc

C那些事之grpc小Demo github上比较火的rpc有grpc、brpc&#xff0c;腾讯内部比较牛逼的trpc等等&#xff0c;这些rpc支持不同的语言、不同平台。今天来聊聊如何使用grpc&#xff0c;从一个简单的demo入手&#xff0c;整个项目使用CMake构建&#xff0c;一个非常标准的rpc项目管…

公园气象站——观测实时气象,保障游客安全

公园气象站是一种用于监测和记录气象数据的系统。在公园内设置公园气象站可以帮助我们了解公园内的气候状况&#xff0c;包括空气湿度、空气温度、风速和风向等参数。这些数据是公园管理、游客安全和环境保护等方面重要的辅助依据。 负氧离子监测&#xff1a;负氧离子是指空气…

Serverless Framework 亚马逊云(AWS)中国地区部署指南

Serverless Framework 亚马逊云(AWS)中国地区部署指南 Serverless Framework 亚马逊云(AWS)中国地区部署指南 前言前置准备 1. 账号的注册2. 全局安装 serverless3. 设置你的系统环境变量4. 设置部署凭证 快速部署一个 hello world 创建入口函数 index.js event 参数context 参…

学习Bootstrap 5的第四天

目录 表格 基础表格 实例 条纹表格 实例 带边框表格 实例 有悬停效果的行 实例 黑色/深色表格 实例 黑色/深色条纹表格 实例 可悬停的黑色/深色表格 实例 无边框表格 实例 上下文类 可用的上下文类&#xff1a; 实例 表头颜色 实例 小型表格 实例 响应…

SpringBoot实现Excel导入导出

话不多说&#xff0c;直接上代码 依赖文档 找到pom文件&#xff0c;如下图所示 引入需要的依赖 <!-- hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.20</version>&…

如何利用顶级AI简历工具优化求职过程

追求梦想工作既是挑战又是机会。而在这一竞争激烈的职场中&#xff0c;拥有一份完美的简历成为与雇主初次互动的黄金名片。但问题是&#xff0c;如何才能使简历真正脱颖而出&#xff1f; 为何简历如此关键? 很多时候&#xff0c;简历的影响力被忽视&#xff0c;尽管它实际上…

2023年下半年广州/深圳软考(中/高级)认证报名,当然弘博创新

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…

新晋开源项目 DisJob 加入 Dromara 社区,分布式任务调度框架

作者简介 网名Ponfee&#xff0c;Dromara开源组织成员&#xff0c;dromara/disjob项目作者。在国内多个一线大厂待过&#xff0c;有过后端、全栈、大数据等相关工作经历。 关于Disjob Disjob是天然为支持分布式长任务执行而设计的&#xff0c;它除了具备常规的任务调度功能外…

K210-关于K210基本操作

1.点亮RGB灯 from modules import ybrgb RGB ybrgb() #设置RGB颜色&#xff1a;RGB.set(r, g, b) #参数r控制红灯开关&#xff0c; #参数g控制绿灯开关&#xff0c; #参数b控制蓝灯开关&#xff0c; #输入0表示关闭&#xff0c;输入1表示开启。 RGB.set(1, 0, 0)2.按键功能 …

【Arduino实验笔记】机械臂篇(二) 开关控制LED灯

文章目录 前言硬件介绍实物接线图软件实现库函数介绍程序代码 下一篇的目标总结 前言 本章节介绍如何通过按键控制LED灯&#xff0c;在上一章节中&#xff0c;我们学习了如何控制IO输出电平。而本章节&#xff0c;我们将学会如何读取IO输入的电平。 硬件介绍 观察独立按键&am…

Taurus: 面向机器学习的数据面架构

日益复杂的网络和多样化的工作负载要求网络内置更多的自动化决策能力&#xff0c;通过可编程网络设备在用户面支持机器学习工作负载就是一个可能的选项&#xff0c;本文提出了一种支持用户面推理的架构设计&#xff0c;相对控制面机器学习的性能有数量级的提升。原文: Taurus: …

联想集团财报不及华尔街预期,财务业绩恐将继续恶化

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 华尔街对联想集团财报的预测 在联想集团&#xff08;00992&#xff09;公布2024财年第一季度财务业绩之前&#xff0c;华尔街分析师就曾预测&#xff0c;联想集团的收入和利润将实现强劲增长。 具体而言&#xff0c;根据S&…

VB6.0 设置窗体的默认焦点位置在 TextBox 中

文章目录 VB6.0 窗体的加载过程确定指针的焦点位置添加代码效果如下未设置指定焦点已设置焦点 VB6.0 窗体的加载过程 在VB6.0中&#xff0c;窗体&#xff08;Form&#xff09;加载时会触发多个事件&#xff0c;这些事件按照特定的顺序执行。下面是窗体加载过程中常见事件的执行…

高忆管理:光刻胶概念强势拉升,同益股份、格林达涨停

光刻胶概念5日盘中强势拉升&#xff0c;截至发稿&#xff0c;同益股份、格林达涨停&#xff0c;波长光电、晶瑞电材涨超7%&#xff0c;容大感光涨逾5%&#xff0c;华懋科技、茂莱光学、苏大维格、南大光电等均走强。 音讯面上&#xff0c;据新加坡《联合早报》网站9月2日报导&…

Android jni引用第三方so动态库和.a静态库并且调用(c)方法

最近花了一周时间来入门学习 Android JNI方面的知识&#xff0c;因为后续的工作很多需要用到c c库&#xff0c;我需要用jni来包装一下c函数&#xff0c;来提供给上次java调用。总之多学点知识对自己有好处。 案例效果&#xff1a; 上文我们讲解了 android studio cmake生成.a…

Python可复用函数的 6 种最佳实践

对于在一个有各种角色的团队中工作的数据科学家来说&#xff0c;编写干净的代码是一项必备的技能&#xff0c;因为&#xff1a; 清晰的代码增强了可读性&#xff0c;使团队成员更容易理解和贡献于代码库。 清晰的代码提高了可维护性&#xff0c;简化了调试、修改和扩展现有代码…