2021-10-18 三、栈

数据结构 专栏收录该内容
7 篇文章 1 订阅

1. 栈的定义

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

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

1.1 数组实现栈的设计思路

在这里插入图片描述
定义一个 top指针 来表示栈顶的下标位置,初始化 为 -1

  1. 入栈的操作:当有数据加入到栈时,top上移,然后将数据放入到top的位置
  2. 出栈的操作:弹出栈顶所在位置的数据,top下移

1.2 代码实现

// 数组表示栈的实体类
public class StackArray {
    /**
     * 栈的大小
     */
    private final int maxSize;
    /**
     * 数组模拟栈,数据就放在该数组
     */
    private final int[] stack;
    /**
     * top表示栈顶的下标,初始化为-1
     */
    private int top = -1;

    public StackArray(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

    /**
     * 栈满
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }

    /**
     * 栈空
     */
    public boolean isEmpty() {
        return top == -1;
    }

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

    /**
     * 出栈-pop,将栈顶的数据返回
     */
    public int pop() {
        // 判断是否为空
        if (isEmpty()) {
            throw new RuntimeException("栈空,无法取!!");
        }

        return stack[top--];
    }

    /**
     * 遍历栈
     */
    public void list() {
        // 判空
        if (isEmpty()) {
            System.out.println("当前栈没有数据,无法遍历");
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}
// 测试类
public class Demo9 {
    public static void main(String[] args) {
        // 先创建一个ArrayStack对象->表示栈
        StackArray stack = new StackArray(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("出栈的数据是 %d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~~");
    }
}

输出结果


show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
show
当前栈没有数据,无法遍历
show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
push
请输入一个数
1
show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
show
stack[0]=1
show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
push
请输入一个数
3
show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
show
stack[1]=3
stack[0]=1
show: 表示显示栈
exit: 退出程序
push: 表示添加数据到栈(入栈)
pop: 表示从栈取出数据(出栈)
请输入你的选择
pop
出栈的数据是 3

1.3 栈的应用场景

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

2. 栈实现综合计算器

2.1 设计思路

在这里插入图片描述

  1. 通过一个 index 值(索引),来遍历我们的表达式,如果发现是一个数字, 就直接入数栈,如果发现扫描到是一个符号, 就分如下情况:
    1 如果发现当前的符号栈为空,就直接入栈
    2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop出一个符号,进行运算。将得到结果,入数栈,然后再将当前的操作符入符号栈。如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈。
    注意:乘法和除法的优先级大于加法和减法

  2. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。最后在数栈只有一个数字,就是表达式的结果。

2.2 代码实现

public class StackArray {
    /**
     * 栈的大小
     */
    private final int maxSize;
    /**
     * 数组模拟栈,数据就放在该数组
     */
    private final int[] stack;
    /**
     * top表示栈顶的下标,初始化为-1
     */
    private int top = -1;

    public StackArray(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

    /**
     * 栈满
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }

    /**
     * 栈空
     */
    public boolean isEmpty() {
        return top == -1;
    }

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

    /**
     * 出栈-pop,将栈顶的数据返回
     */
    public int pop() {
        // 判断是否为空
        if (isEmpty()) {
            throw new RuntimeException("栈空,无法取!!");
        }

        return stack[top--];
    }

    /**
     * 遍历栈
     */
    public void list() {
        // 判空
        if (isEmpty()) {
            System.out.println("当前栈没有数据,无法遍历");
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }

    // ==================== 下面为计算器需要的方法 =====================

    /**
     * 查看栈顶的值,不是pop
     */
    public int peek() {
        return stack[top];
    }

    /**
     * 返回运算符优先级,数字越大,优先级越高
     */
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 2;
        } else if (oper == '+' || oper == '-') {
            return 1;
        }
        // 不满足的情况
        return -1;
    }

    /**
     * 判断是否为一个运算符
     */
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    /**
     * 计算方法
     */
    public int cal(int num1, int num2, int oper) {
        int res = 0; // res 用于存放计算的结果
        switch (oper) {
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }
}
public class Demo10 {
    public static void main(String[] args) {
        // expression 表达式
        String expression = "300-8*4/2";
        // 创建两个栈,一个数栈,一个符号栈
        StackArray numStack = new StackArray(10);
        StackArray operStack = new StackArray(10);
        // 定义需要的相关变量
        //用于扫描
        int index = 0;
        int num1;
        int num2;
        int oper;
        int res;
        char ch = ' '; // 将每次扫描得到char保存到ch
        String keepNum = ""; // 用于拼接 多位数
        // 开始while循环的扫描expression
        do {
            // 依次得到expression 的每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            // 判断ch是什么,然后做相应的处理
            if (operStack.isOper(ch)) {
                // 如果是运算符 判断当前的符号栈是否为空
                if (!operStack.isEmpty()) {
                    // 如果符号栈有操作符,就进行比较
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        // 比较规则:如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数来
                        // 再从符号栈中pop出一个操作符,进行运算,将得到的结果入数栈,然后将当前操作符入符号栈
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1, num2, oper);
                        // 将运算结果,入数栈
                        numStack.push(res);
                        // 操作符入操作符栈
                        operStack.push(ch);
                    } else {
                        // 如果当前操作符优先级大于操作符栈顶的值优先级,则直接入操作符栈
                        operStack.push(ch);
                    }
                } else {
                    // 如果操作符栈为空,则直接入栈
                    operStack.push(ch);
                }
            } else {
                // 如果是数,则入数栈

                //numStack.push(ch - 48); // ‘1’ - 48 = 1 单位数运算时可以直接把数字入栈
                // 1、在处理多位数的时候,不能遇到数就直接入栈,因为可能数多位数
                // 2、在处理数时,需要向expression 表达式的 index 再往后移动 一位看 一下,如果是数 则继续扫描,
                //如果遇到的是符号,就可以直接入数栈了
                // 3、因此需要一个辅助的字符串拼接判断

                // 用于拼接数字
                keepNum += ch;

                // 如果ch已经是expression的最后一位,就直接入栈
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                } else {
                    // 判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
                    // 注意是看后一位,不是index++
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        // 如果后一位是运算符,则入栈,否则就累计
                        numStack.push(Integer.parseInt(keepNum));
                        // 清空(注意!!!)
                        keepNum = "";
                    }
                }
            }
            // 让index + 1, 并判断是否扫描到expression最后
            index++;
        } while (index < expression.length());

        // 当表达式扫描计算完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行
        while (!operStack.isEmpty()) {
            // 如果符号栈为空,则计算到最后的结果, 数栈中只有一个数字【结果】
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1, num2, oper);
            numStack.push(res);//入栈
        }
        // 将数栈的最后数,pop出,就是结果
        int result = numStack.pop();
        System.out.printf("表达式 %s = %d", expression, result);
    }
}

输出结果

表达式 300-8*4/2 = 284

3. 波兰表达式

3.1 简介

中缀表达式就是传统的数学表达式:2+3*3=?
中缀表达式:(3+4)×5-6 对应的前缀表达式(波兰表达式)就是 - × + 3 4 5 6
后缀表达式(逆波兰表达式),就是前缀的逆向

3.2 前缀表达式

3.2.1 中缀表达式转前缀表达式

操作步骤:

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

示例
1+((2+3)×4)-5 中缀转前缀 -+1×+2345
在这里插入图片描述

3.2.2 代码实现

/**
     * 中缀表达式转换成前缀表达式
     */
    private static List<String> parsePrefixExpresionList(List<String> list) {
        // 运算符栈S1
        Stack<String> s1 = new Stack<>();
        // 存储中间计算和最终结果的栈S2
        Stack<String> s2 = new Stack<>();
        // 从右到左遍历list
        for (int i = list.size() - 1; i >= 0; i--) {
            String item = list.get(i);
            // 若是一个数字(包括多位数和1-9),加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if (")".equals(item)) {
                // 如果当前字符串是")"可以直接入栈
                s1.push(item);
            } else if (s1.size() != 0 && ")".equals(s1.peek())) {
                // 如果栈顶为")",也可以直接入栈
                s1.push(item);
            } else if ("(".equals(item)) {
                // 如果是左括号的话则依次弹出s1栈顶的运算符,并压入s2,直到遇到右括号为止
                while (!")".equals(s1.peek())) {
                    s2.push(s1.pop());
                }
                // 弹出运算符栈中的右括号,消除括号
                s1.pop();
            } else {
                // 如果当前s1栈顶运算符大于运算符的优先级, 将s1栈顶的运算符弹出并加入到s2中,
                // 然后继续与s1中新的栈顶运算符相比较
                while (s1.size() != 0 && Operation.getValue(s1.peek()) > Operation.getValue(item)) {
                    s2.add(s1.pop());
                }
                // 将当前运算符直接压栈
                s1.push(item);
            }
        }
        // 将s1中剩余的运算符弹出并加入s2
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }
        final ArrayList<String> arrayList = new ArrayList<>();
        while (s2.size() != 0) {
            arrayList.add(s2.pop());
        }
        return arrayList;
    }

3.2.3 前缀表达式的计算方法

操作步骤:

  1. 对前缀表达式从后向前扫描,并设定一个操作数栈S1
  2. 如果是操作数,则将其直接入栈
  3. 如果是操作符,则从栈中弹出操作符对应的操作数进行运算,并将计算结果压栈
  4. 直至从右到左扫描完毕整个前缀表达式,这时操作数栈中应该只有一个元素,该元素的值则为前缀表达式的计算结果。

3.2.4 代码实现

 /**
     * 前缀表达式的计算 - + 1 × + 2 3 4 5
     */
    public static int calculate(List<String> ls) {
        // 设定一个操作数栈S1
        Stack<String> stack = new Stack<>();

        for (int i = ls.size() - 1; i >= 0; i--) {
            // 对前缀表达式从后向前扫描
            String item = ls.get(i);
            // 如果是操作数,则将其直接入栈
            if (item.matches("\\d+")) {
                // 入栈
                stack.push(item);
            } else {
                // 如果是操作符,则从栈中弹出操作符对应的操作数进行运算,并将计算结果压栈
                // 如果是运算符,pop出两个数,并运算, 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                // 接收结果
                int res;
                switch (item) {
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num2 - num1;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num2 / num1;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                // 运算后入栈
                stack.push("" + res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

3.2.5 完整代码

public class Demo11Q {
    public static void main(String[] args) {
        // 将一个中缀表达式转成前缀表达式 1+((2+3)×4)-5 => 转成 - + 1 × + 2 3 4 5
        String str = "1+((2+3)*4/5)-5";
        // 因为直接对str 进行操作,不方便,因此 先将中缀的表达式转换成对应的List
        List<String> infixExpressionList = toInfixExpressionList(str);
        System.out.println("中缀表达式对应的List=" + infixExpressionList);
        // 将得到的中缀表达式对应的List 转成 前缀表达式对应的List
        List<String> prefixExpresionList = parsePrefixExpresionList(infixExpressionList);
        System.out.println("前缀表达式对应的List=" + prefixExpresionList);
        // 计算
        System.out.printf("expression=%d", calculate(prefixExpresionList));
    }

    /**
     * 前缀表达式的计算 - + 1 × + 2 3 4 5
     */
    public static int calculate(List<String> ls) {
        // 设定一个操作数栈S1
        Stack<String> stack = new Stack<>();

        for (int i = ls.size() - 1; i >= 0; i--) {
            // 对前缀表达式从后向前扫描
            String item = ls.get(i);
            // 如果是操作数,则将其直接入栈
            if (item.matches("\\d+")) {
                // 入栈
                stack.push(item);
            } else {
                // 如果是操作符,则从栈中弹出操作符对应的操作数进行运算,并将计算结果压栈
                // 如果是运算符,pop出两个数,并运算, 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                // 接收结果
                int res;
                switch (item) {
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num2 - num1;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num2 / num1;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                // 运算后入栈
                stack.push("" + res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

    /**
     * 中缀表达式转换成前缀表达式
     */
    private static List<String> parsePrefixExpresionList(List<String> list) {
        // 运算符栈S1
        Stack<String> s1 = new Stack<>();
        // 存储中间计算和最终结果的栈S2
        Stack<String> s2 = new Stack<>();
        // 从右到左遍历list
        for (int i = list.size() - 1; i >= 0; i--) {
            String item = list.get(i);
            // 若是一个数字(包括多位数和1-9),加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if (")".equals(item)) {
                // 如果当前字符串是")"可以直接入栈
                s1.push(item);
            } else if (s1.size() != 0 && ")".equals(s1.peek())) {
                // 如果栈顶为")",也可以直接入栈
                s1.push(item);
            } else if ("(".equals(item)) {
                // 如果是左括号的话则依次弹出s1栈顶的运算符,并压入s2,直到遇到右括号为止
                while (!")".equals(s1.peek())) {
                    s2.push(s1.pop());
                }
                // 弹出运算符栈中的右括号,消除括号
                s1.pop();
            } else {
                // 如果当前s1栈顶运算符大于运算符的优先级, 将s1栈顶的运算符弹出并加入到s2中,
                // 然后继续与s1中新的栈顶运算符相比较
                while (s1.size() != 0 && Operation.getValue(s1.peek()) > Operation.getValue(item)) {
                    s2.add(s1.pop());
                }
                // 将当前运算符直接压栈
                s1.push(item);
            }
        }
        // 将s1中剩余的运算符弹出并加入s2
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }
        final ArrayList<String> arrayList = new ArrayList<>();
        while (s2.size() != 0) {
            arrayList.add(s2.pop());
        }
        return arrayList;
    }

    /**
     * 将中缀表达式字符串转成List
     */
    public static List<String> toInfixExpressionList(String s) {
        // 定义一个List,存放中缀表达式
        List<String> ls = new ArrayList<>();
        // 定义一个指针,用于遍历
        int index = 0;
        // 拼接多位数
        String str;
        do {
            // 若 当前字符 数字,则加入 ls 集合
            if (s.charAt(index) > '9' || s.charAt(index) < '0') {
                ls.add("" + s.charAt(index));
                // 指针后移
                index++;
            } else {
                // 如果是一个数字,还得考虑多位数与否
                // 先将str 置成"",每次拼接前先清空上次的记录
                str = "";
                // 如果是数字,并且表达式内还有值
                while (index < s.length() && s.charAt(index) >= '0' && s.charAt(index) <= '9') {
                    // 拼接多位数
                    str += s.charAt(index);
                    index++;
                }
                ls.add(str);
            }
        } while (index < s.length());
        return ls;
    }
}

3.3 后缀表达式

3.3.1 中缀表达式转后缀表达式

操作步骤:

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

示例
中缀转后缀1 + ( ( 2 + 3 ) × 4 ) - 5 => 1 2 3 + 4 × + 5 –
在这里插入图片描述

3.3.2 代码实现

 /**
     * 中缀表达式 转 后缀表达式
     * 1 + ( ( 2 + 3 ) × 4 ) - 5 => 转成 1 2 3 + 4 × + 5 –
     */
    public static List<String> parseSuffixExpresionList(List<String> ls) {
        // 运算符栈
        Stack<String> s1 = new Stack<>();
        // 存储中间计算和最终结果的栈S2,如果使用栈的话需要将字符串逆序,所以直接使用list
        List<String> s2 = new ArrayList<>();

        // 遍历中缀表达式
        for (String item : ls) {
            // 若是一个数字(包括多位数和1-9),加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if ("(".equals(item)) {
                // 如果当前的字符串是"("可以直接入栈
                s1.push(item);
            } else if (s1.size() != 0 && "(".equals(s1.peek())) {
                // 栈顶为"(" 可以直接入栈
                s1.push(item);
            } else if (")".equals(item)) {
                // 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止
                while (!"(".equals(s1.peek())) {
                    s2.add(s1.pop());
                }
                // 将 "(" 弹出 s1栈, 消除小括号
                s1.pop();
            } else {
                // 如果当前s1栈顶运算符大于等于运算符的优先级, 将s1栈顶的运算符弹出并加入到s2中,
                // 然后继续与s1中新的栈顶运算符相比较
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
                    s2.add(s1.pop());
                }
                // 将当前运算符直接压栈
                s1.push(item);
            }
        }

        // 将s1中剩余的最后一个运算符弹出并加入s2
        if (s1.size() != 0) {
            s2.add(s1.pop());
        }
        // 因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
        return s2;
    }

3.3.3 后缀表达式的计算方式

操作步骤:

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

3.3.4 代码实现

/**
     * 对逆波兰表达式进行运算
     */
    public static int calculate(List<String> ls) {
        // 创建一个栈,用于存储依次弹出计算
        Stack<String> stack = new Stack<>();

        // 遍历ls,计算入栈
        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;
                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("运算符有误");
                }
                // 运算后入栈
                stack.push("" + res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

3.3.5 完整代码

public class Demo12 {
    public static void main(String[] args) {
        String str = "1+((2+3)*4)-5";

        // 将字符串拆分到List当中
        List<String> list = toInfixExpressionList(str);
        System.out.println("中缀表达式对应的List=" + list);

        // 将得到的中缀表达式对应的List 转成 后缀表达式对应的List
        List<String> suffixExpresionList = parseSuffixExpresionList(list);
        System.out.println("后缀表达式对应的List=" + suffixExpresionList);

        System.out.printf("expression=%d", calculate(suffixExpresionList));
    }

    /**
     * 中缀表达式 转 后缀表达式
     * 1 + ( ( 2 + 3 ) × 4 ) - 5 => 转成 1 2 3 + 4 × + 5 –
     */
    public static List<String> parseSuffixExpresionList(List<String> ls) {
        // 运算符栈
        Stack<String> s1 = new Stack<>();
        // 存储中间计算和最终结果的栈S2,如果使用栈的话需要将字符串逆序,所以直接使用list
        List<String> s2 = new ArrayList<>();

        // 遍历中缀表达式
        for (String item : ls) {
            // 若是一个数字(包括多位数和1-9),加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if ("(".equals(item)) {
                // 如果当前的字符串是"("可以直接入栈
                s1.push(item);
            } else if (s1.size() != 0 && "(".equals(s1.peek())) {
                // 栈顶为"(" 可以直接入栈
                s1.push(item);
            } else if (")".equals(item)) {
                // 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止
                while (!"(".equals(s1.peek())) {
                    s2.add(s1.pop());
                }
                // 将 "(" 弹出 s1栈, 消除小括号
                s1.pop();
            } else {
                // 如果当前s1栈顶运算符大于等于运算符的优先级, 将s1栈顶的运算符弹出并加入到s2中,
                // 然后继续与s1中新的栈顶运算符相比较
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
                    s2.add(s1.pop());
                }
                // 将当前运算符直接压栈
                s1.push(item);
            }
        }

        // 将s1中剩余的最后一个运算符弹出并加入s2
        if (s1.size() != 0) {
            s2.add(s1.pop());
        }
        // 因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
        return s2;
    }

    /**
     * 将中缀表达式字符串转成List
     */
    public static List<String> toInfixExpressionList(String s) {
        // 定义一个List,存放中缀表达式
        List<String> ls = new ArrayList<>();
        // 定义一个指针,用于遍历
        int index = 0;
        // 拼接多位数
        String str;
        do {
            // 若 当前字符 数字,则加入 ls 集合
            if (s.charAt(index) > '9' || s.charAt(index) < '0') {
                ls.add("" + s.charAt(index));
                // 指针后移
                index++;
            } else {
                // 如果是一个数字,还得考虑多位数与否
                // 先将str 置成"",每次拼接前先清空上次的记录
                str = "";
                // 如果是数字,并且表达式内还有值
                while (index < s.length() && s.charAt(index) >= '0' && s.charAt(index) <= '9') {
                    // 拼接多位数
                    str += s.charAt(index);
                    index++;
                }
                ls.add(str);
            }
        } while (index < s.length());
        return ls;
    }


    /**
     * 对逆波兰表达式进行运算
     */
    public static int calculate(List<String> ls) {
        // 创建一个栈,用于存储依次弹出计算
        Stack<String> stack = new Stack<>();

        // 遍历ls,计算入栈
        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;
                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("运算符有误");
                }
                // 运算后入栈
                stack.push("" + res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}
上一篇总目录下一篇
二、链表数据结构篇(Java)目录四、递归
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

梦开始的地方丶

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值