栈的一个实际需求

请输入一个表达式
计算式: [722-5+1-53-3] 点击计算[如下图]
image.png
请问: 计算机底层是如何运算得到结果的? 注意: 不是简单地把算式列出运算,因为我们看这个算式 [7
22-5+1-53-3] 但是计算机是则么理解这个算式的(对计算机而言, 他接收到的就是一个字符串), 我们讨论的是这个问题, -> 栈

栈的介绍

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

image.png
image.png

栈的应用场景

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

    栈的快速入门

    需求

  6. 用数组模拟栈的使用,由于栈是一种有序列表,当然可以使用数组的结构来存储栈的数据内容,下面我们就使用数组模拟栈的出栈,入栈等操作

    思路分析

    实际思路分析并画出示意图
    image.png
    实现栈的思路分析

  7. 使用数组模拟栈

  8. 定义一个top来表示栈顶初始化为-1
  9. 入栈的操作: 当 有数据加入到栈时, Top++, stack[top] = data
  10. 出栈的操作: int value = stack[top]; top—;return value

    代码实现(使用数组模拟栈)

    ```java package com.dance.stack;

public class ArrayStackDemo { public static void main(String[] args) { ArrayStack arrayStack = new ArrayStack(10); arrayStack.show(); for (int i = 0; i < 12; i++) { arrayStack.push(i+1); } arrayStack.show(); while (!arrayStack.isEmpty()){ System.out.println(“出栈:” + arrayStack.pop()); } arrayStack.show(); } }

/**

  • 定义一个数组栈 */ class ArrayStack{

    /**

    • 栈 */ private final int[] stack;

      /**

    • 栈顶 */ private int top = -1;

      /**

    • 栈的最大容量 */ private final int maxSize;

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

      /**

    • 是否栈满
    • @return true:满了 false:没满 */ public boolean isFull(){ return top == maxSize -1; }

      /**

    • 是否栈空
    • @return ture:空了 false:没空 */ public boolean isEmpty(){ return top == -1; }

      /**

    • 压栈
    • @param newValue 值 */ public void push(int newValue){ if(isFull()){

      1. System.out.println("栈满了,无法加入数据:"+newValue);
      2. return;

      } top++; stack[top] = newValue; }

      /**

    • 弹出
    • @return 栈顶数据 */ public int pop(){ if(isEmpty()){

       throw new RuntimeException("栈空了,无法弹出数据");
      

      } int value = stack[top]; top—; return value; }

      /**

    • 遍历栈 */ public void show(){ if(isEmpty()){
       System.out.println("栈为空,无法遍历");
       return;
      
      } for (int i = top; i >= 0; i—) {
       System.out.print(stack[i] + "\t");
      
      } System.out.println(); }

}

执行结果
```java
栈为空,无法遍历
栈满了,无法加入数据:11
栈满了,无法加入数据:12
10    9    8    7    6    5    4    3    2    1    
出栈:10
出栈:9
出栈:8
出栈:7
出栈:6
出栈:5
出栈:4
出栈:3
出栈:2
出栈:1
栈为空,无法遍历

栈实现综合计算器(中缀表达式)

需求

实现中缀表达式计算,先实现单位数计算,后扩展成多位

思路分析

image.png
image.png

代码实现

package com.dance.stack;

public class CalculatorDemo {

    public static void main(String[] args) {
        // 定义表达式
        String expression = "3+2*6-2";
        System.out.println("计算结果: " + expressionCalculator(expression));
    }

    /**
     * 表达式计算
     * @param expression 表达式
     * @return 结果
     */
    public static int expressionCalculator(String expression) {
        // 创建两个栈,一个用来存储数字,一个用来存储操作符
        // 数字栈
        ArrayStackExt numStack = new ArrayStackExt(10);
        // 操作符栈
        ArrayStackExt operationStack = new ArrayStackExt(10);
        // 扫描并计算中缀表达式
        scannerExpression(expression, numStack, operationStack);
        // 计算顺序的操作
        return calculatorOther(numStack, operationStack);
    }

    /**
     * 表达式扫描
     * @param expression 表达式
     * @param numStack 数栈
     * @param operationStack 操作符栈
     */
    public static void scannerExpression(String expression, ArrayStackExt numStack, ArrayStackExt operationStack) {
        // 定义扫描索引
        int index = 0;
        char ch = ' '; // 将每次扫描得到的char保存到ch
        // 开始循环扫描表达式
        while (index < expression.length()) {
            // 循环的到每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            // 判断是不是操作符
            if (ArrayStackExt.isOperation(ch)) {
                // 是操作符
                // 判断当前符号栈是不是空
                if (operationStack.isEmpty()) {
                    // 是空
                    operationStack.push(ch);
                } else {
                    // 不是空
                    // 获取栈中的操作符
                    int operation = operationStack.pop();
                    // ch 就是新的操作符
                    // 进行比较
                    if (operationStack.comparator(ch, operation)) {
                        // 小于等于
                        // 计算
                        // 将返回的结果压入数栈
                        numStack.push(numStack.cal(numStack.pop(), numStack.pop(), operation));
                        // 将当前的操作符 压入操作符栈
                        operationStack.push(ch);
                    } else {
                        // 大于
                        // 将原来弹出栈的操作符重新压入栈中
                        operationStack.push(operation);
                        // 直接压入符号栈
                        operationStack.push(ch);
                    }
                }
            } else {
                // 不是操作符,是数字
                // 直接压入数栈(需要将字符转化为数字)
                numStack.push(Integer.parseInt(String.valueOf(ch)));
            }
            index++;
        }
    }

    /**
     * 计算剩余的操作符
     * @param numStack 数栈
     * @param operationStack 操作符栈
     * @return 结果
     */
    public static int calculatorOther(ArrayStackExt numStack, ArrayStackExt operationStack) {
        // 当扫描完成后,同时操作符栈中的操作符是顺序的
        while (!operationStack.isEmpty()) {
            numStack.push(numStack.cal(numStack.pop(), numStack.pop(), operationStack.pop()));
        }
        return numStack.pop();
    }
}

class ArrayStackExt extends ArrayStack {

    public ArrayStackExt(int size) {
        super(size);
    }

    /**
     * 判断操作符的优先级
     * @param left 左侧操作符
     * @param right 右侧操作符
     * @return 左侧是否小于等于右侧
     */
    public boolean comparator(int left, int right) {
        return priority(left) <= priority(right);
    }

    // 返回运算符的优先级,优先级是程序员来确定的,优先级使用数字表示,数字越大,优先级越高
    public int priority(int operation) {
        switch (operation) {
            case '*':
            case '/':
                return 1;
            case '+':
            case '-':
                return 0;
            default:
                throw new RuntimeException("操作符错误");
        }
    }

    /**
     * 判断是不是操作符
     * @param operation 操作符
     * @return true:是 false:不是
     */
    public static boolean isOperation(char operation) {
        return operation == '+' || operation == '-' || operation == '*' || operation == '/';
    }

    /**
     * 计算
     *
     * @param num1      左边数字
     * @param num2      右边数字
     * @param operation 操作符
     * @return 结果
     */
    public int cal(int num1, int num2, int operation) {
        switch (operation) {
            case '+':
                return num1 + num2;
            case '-':
                return num2 - num1; // 注意顺序,加和乘无所谓,因为栈的原因num2需要在前面
            case '*':
                return num1 * num2;
            case '/':
                return num2 / num1;
            default:
                throw new RuntimeException("计算符号错误");
        }
    }
}

执行结果

计算结果: 13

实现多位数计算(扩展)

package com.dance.stack;

public class CalculatorDemo {

    public static void main(String[] args) {
        // 定义表达式
        String expression = "30+2*6-2";
        System.out.println("计算结果: " + expressionCalculator(expression));
    }

    /**
     * 表达式计算
     *
     * @param expression 表达式
     * @return 结果
     */
    public static int expressionCalculator(String expression) {
        ......
    }

    /**
     * 表达式扫描
     *
     * @param expression     表达式
     * @param numStack       数栈
     * @param operationStack 操作符栈
     */
    public static void scannerExpression(String expression, ArrayStackExt numStack, ArrayStackExt operationStack) {
        // 定义扫描索引
        int index = 0;
        char ch = ' '; // 将每次扫描得到的char保存到ch
        // 定义数字字符串 用于解决多位数字问题
        StringBuilder keepNum = null;
        // 开始循环扫描表达式
        while (index < expression.length()) {
            keepNum = new StringBuilder();
            // 循环的到每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            // 判断是不是操作符
            if (ArrayStackExt.isOperation(ch)) {
                ......
            } else {
                // 不是操作符,是数字
                // 直接压入数栈(需要将字符转化为数字)(只能处理单位数)
//                numStack.push(Integer.parseInt(String.valueOf(ch)));
                // 处理多位数,
                /*
                分析思路:
                1:当处理多位数时,不能发现是一个数就直接压栈,因为他可能是多位数
                2:在处理数,需要向expression的表达式的index后继续判断是不是数字, 如果是数字就继续扫描,如果是符号,就截止到符号的前一位
                3:所以我们定义一个字符串用于拼接
                 */
                keepNum.append(ch);
                for (int i = index+1; i < expression.length(); i++) {
                    char after = expression.substring(i, i + 1).charAt(0);
                    if(ArrayStackExt.isOperation(after)){
                        break;
                    }else{
                        index++;
                        keepNum.append(after);
                    }
                }
                // 转为数字
                numStack.push(Integer.parseInt(keepNum.toString()));
            }
            index++;
        }
    }

    /**
     * 计算剩余的操作符
     *
     * @param numStack       数栈
     * @param operationStack 操作符栈
     * @return 结果
     */
    public static int calculatorOther(ArrayStackExt numStack, ArrayStackExt operationStack) {
        // 当扫描完成后,同时操作符栈中的操作符是顺序的
        while (!operationStack.isEmpty()) {
            numStack.push(numStack.cal(numStack.pop(), numStack.pop(), operationStack.pop()));
        }
        return numStack.pop();
    }
}

class ArrayStackExt extends ArrayStack {

    ......
}

执行结果

计算结果: 40

前缀, 中缀, 后缀表达式(逆波兰表达式):扩展

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

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

    前缀表达式的计算机求值

    从右到左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们进行计算,并将结果重新压入数栈,重复上述过程直到表达式的最左端,最后运算得出的值即为表达式的结果
    例如:
    (3+4)X5-6 对应的前缀表达式就是- X + 3 4 5 6, 针对前缀表达式求值步骤如下

  3. 从右至左扫描, 将 6 5 4 3压入堆栈

  4. 遇到+运算符, 因此弹出3和4(3为栈顶元素,4为次顶元素), 计算出3+4的值得7,再将7压入数栈
  5. 接下来是X运算符,因此弹出7,5,计算7X5=35,将35压入数栈
  6. 最后是-运算符,计算出35-6,即29,由此得出最终结果

    中缀表达式

  7. 中缀表达式就是常见的运算表达式 如(3+4)X5-6

  8. 中缀表达式的求值是我们最熟悉的,但是对计算机来说却不好操作,因此在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转为后缀表达式)

    后缀表达式

  9. 后缀表达式又称为逆波兰表达式,与前缀表达式相似,只是运算位于操作数之后

  10. 中缀转后缀例子:(3+4)X5-6 对应的后缀表达式就是 3 4 + 5 X 6 -

image.png

后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入数栈,遇到运算符时,弹出栈顶的两个数,进行计算后并将结果重新压入数栈,重复上述过程直到最右端,最后运算得出的值,就是表达式的结果
例如: (3+4)X5-6 对应的后缀表达式就是 3 4 + 5 X 6 -

  1. 从左至右扫描, 将3 4 压入数栈
  2. 遇到 + 运算符,因此弹出4 和 3 计算出 3 + 4 的值 7后,将7压入数栈
  3. 将5压入数栈
  4. 接下来就是X运算符,因此弹出 5 7,计算 5 X 7 = 35 后将35压入数栈
  5. 将6压入数栈
  6. 最后是-运算符,计算出35 - 6 = 29 即为结果

    逆波兰计算器

    需求

  7. 输入一个逆波兰表达式,使用栈(Stack),计算其结果

  8. 支持小括号和多位整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算
  9. 思路分析

    代码实现

    ```java package com.dance.stack;

import java.util.Arrays; import java.util.List;

public class PolandNotation { public static void main(String[] args) { // 先定义一个逆波兰表达式 // (3+4)X5-6 => 3 4 + 5 x 6 - // 为了扫描方便,使用空格隔开 String suffixExpression = “3 4 + 5 * 6 -“; System.out.println(“计算结果: “+ calPoland(suffixExpression)); }

public static int calPoland(String expression) {
    // 将表达式通过空格转为list
    List<String> expressList = Arrays.asList(expression.split(" "));
    ArrayStackExt numStack = new ArrayStackExt(10);
    for (String s : expressList) {
        char ch = s.charAt(0);
        if (ArrayStackExt.isOperation(ch)) {
            // 操作符
            // 弹出栈顶的两个数字
            int num1 = numStack.pop();
            int num2 = numStack.pop();
            numStack.push(numStack.cal(num1, num2, ch));
        } else {
            // 将数字直接压入数栈
            numStack.push(Integer.parseInt(String.valueOf(ch)));
        }
    }
    // 弹出结果
    return numStack.pop();
}

}

执行结果
```java
计算结果: 29

中缀表达式转后缀表达式

可以看到,后缀表达式适合计算机进行计算, 但是人就不太好些了,尤其是复杂的, 因此在开发中, 我们需要将中缀表达式转成后缀表达式

具体步骤

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

      举例说明

      将中缀表达式 1+((2+3)4)-5 转换为后缀表达式的过程 如下
      image.png
      因此结果为 1 2 3 + 4
      + 5 -

      代码实现

      ```java package com.dance.stack;

import java.util.Arrays; import java.util.List; import java.util.Stack;

public class PolandNotation { public static void main(String[] args) { // 先定义一个逆波兰表达式 // (3+4)X5-6 => 3 4 + 5 x 6 - // 为了扫描方便,使用空格隔开 // String suffixExpression = “3 4 + 5 6 -“; String middleExpression = “1+((2+3)4)-5”; String suffixExpression = middlePolandToSuffixPoland(middleExpression); System.out.println(“计算结果: “+ calSuffixPoland(suffixExpression)); }

public static String middlePolandToSuffixPoland(String expression){
    List<String> expressList = Arrays.asList(expression.split(""));
    Stack<String> numStack = new Stack<>();
    Stack<String> operStack = new Stack<>();
    ArrayStackExt comparator = new ArrayStackExt(1);
    for (String s : expressList) {
        char ch = s.charAt(0);
        if(ArrayStackExt.isOperation(ch)){
            if(operStack.isEmpty()){
                // 操作符栈为空
                operStack.push(String.valueOf(ch));
            }else{
                String stackInOper = operStack.pop();
                if(stackInOper.equals("(")){
                    // 如果是小括号
                    operStack.push(stackInOper);
                    operStack.push(String.valueOf(ch));
                }else{
                    if(!comparator.comparator(ch,stackInOper.charAt(0))){
                        // 大于栈顶的运算符
                        operStack.push(stackInOper);
                        operStack.push(String.valueOf(ch));
                    }else{
                        // 将栈内的操作符压入数栈
                        numStack.push(stackInOper);
                        // 循环4.1
                        boolean flag = false;
                        while (!operStack.isEmpty()){
                            stackInOper = operStack.pop();
                            if(stackInOper.equals("(")){
                                // 如果是小括号
                                operStack.push(stackInOper);
                                operStack.push(String.valueOf(ch));
                                flag = true;
                                break;
                            }else{
                                if(!comparator.comparator(ch,stackInOper.charAt(0))){
                                    // 大于栈顶的运算符
                                    operStack.push(stackInOper);
                                    operStack.push(String.valueOf(ch));
                                    flag = true;
                                    break;
                                }else{
                                    // 将栈内的操作符压入数栈
                                    numStack.push(stackInOper);
                                }
                            }
                        }
                        // 处理为空也没有压入操作符栈的问题
                        if(!flag){
                            operStack.push(String.valueOf(ch));
                        }
                    }
                }
            }
        }else if(isKh(ch)){
            if(ch == '('){
                operStack.push(String.valueOf(ch));
            }else{
                String res;
                while (!(res = operStack.pop()).equals("(")){
                    numStack.push(res);
                }
            }
        }else{
            // 数字
            numStack.push(String.valueOf(ch));
        }
    }
    // 处理剩余的操作符
    while (!operStack.isEmpty()){
        numStack.push(operStack.pop());
    }
    StringBuffer suffixExpression = new StringBuffer();
    while (!numStack.isEmpty()){
        String pop = numStack.pop();
        suffixExpression.append(pop).append(" ");
    }
    return suffixExpression.reverse().toString().trim();
}

public static boolean isKh(char ch){
    return ch == '(' || ch == ')';
}

public static int calSuffixPoland(String expression) {
    // 将表达式通过空格转为list
    List<String> expressList = Arrays.asList(expression.split(" "));
    ArrayStackExt numStack = new ArrayStackExt(10);
    for (String s : expressList) {
        char ch = s.charAt(0);
        if (ArrayStackExt.isOperation(ch)) {
            // 操作符
            // 弹出栈顶的两个数字
            int num1 = numStack.pop();
            int num2 = numStack.pop();
            numStack.push(numStack.cal(num1, num2, ch));
        } else {
            // 将数字直接压入数栈
            numStack.push(Integer.parseInt(String.valueOf(ch)));
        }
    }
    // 弹出结果
    return numStack.pop();
}

}

执行结果
```java
计算结果: 16

因为是主要写中缀表达式转后缀表达式,就没有考虑多位数和小数点的问题,有喜欢的自己完善一下,老师的代码里面也有完成了的,可以看看,我感觉挺简单的就不写了