1、栈的基本介绍

栈的英文为stack,是一个先入后出的有序列表。栈是限制性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端叫做变化的一端,称为栈顶(Top),固定的一端称为栈底(Bottom)。
 

2、应用场景

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

3、数组模拟栈

package cn.klb.datastructures.stack;

/**
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 * @Description:
 * @Date: Create in 2023/4/1 14:44
 * @Modified By:
 */
public class ArrayStack {
   
     
    private int maxSize;    // 栈容量
    private int[] stack;    // 数组模拟栈
    private int top = -1;   // 栈顶指针

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

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

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

    /**
     * 添加元素到栈
     */
    public void push(int value) {
   
     
        if (isFull()) {
   
     
            System.out.println("栈已满,添加失败!");
        } else {
   
     
            top++;
            stack[top] = value;
        }
    }

    /**
     * 弹出元素
     *
     * @return
     */
    public int pop() {
   
     
        if (isEmpty()) {
   
     
            throw new RuntimeException("栈为空,没有元素!");
        } else {
   
     
            int value = stack[top];
            top--;
            return value;
        }
    }

    /**
     * 查看栈顶元素,但不弹出
     *
     * @return
     */
    public int peak() {
   
     
        if (isEmpty()) {
   
     
            throw new RuntimeException("栈为空,没有元素!");
        } else {
   
     
            int value = stack[top];
            return value;
        }
    }

    /**
     * 显示栈
     */
    public void show() {
   
     
        if (isEmpty()) {
   
     
            System.out.println("栈为空,没什么好显示的。");
        } else {
   
     
            int count = top;
            while (count != -1) {
   
     
                System.out.print(stack[count--] + " ");
            }
            System.out.println();
        }
    }
}

4、栈的运用

4.1 利用栈实现简单整数计算器功能

package cn.klb.datastructures.stack;

/**
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 * @Description: 利用栈实现计算器功能
 * @Date: Create in 2023/4/1 15:57
 * @Modified By:
 */
public class Calculator {
   
     
    private String expression;  // 待计算的表达式
    private ArrayStack numStack = new ArrayStack(10);    // 存放表达式的数字
    private ArrayStack operStack = new ArrayStack(10);   // 存放表达式的操作符
    private int index = 0;  // 用于扫描表达式
    private int result = 0; // 计算结果
    private String keepNum = "";    // 用于拼接两位数以上的数字
    private char ch = ' ';   // 将每次扫描得到的char保存到ch
    private int num1 = 0;   // 操作数1
    private int num2 = 0;   // 操作数2
    private int oper = 0;   // 操作符

    public Calculator(String expression) {
   
     
        this.expression = expression;
    }

    public int count() {
   
     
        // 表达式扫描
        while (true) {
   
     
            ch = this.expression.substring(index, index + 1).charAt(0);
            // 如果扫描到的字符是操作符,则进行处理
            if (isOper(ch)) {
   
     
                // 如果操作符栈不为空,则比较待插入的操作符和top的操作符优先级
                if (!operStack.isEmpty()) {
   
     
                    // 如果扫描到的操作符优先级小于或等于top操作符的优先级,则进行运算
                    if (priority(ch) <= priority(operStack.peak())) {
   
     
                        num2 = numStack.pop();
                        num1 = numStack.pop();
                        oper = operStack.pop();
                        result = cal(num1, oper, num2);
                        numStack.push(result);
                        operStack.push(ch);
                    } else {
   
       // 如果扫描到的操作符优先级大于top操作符的优先级,则直接压入栈
                        operStack.push(ch);
                    }
                } else {
   
       // 如果操作符栈为空,则直接压入栈
                    operStack.push(ch);
                }
            } else {
   
       // 如果扫描到的是数字
                // 因为不知道后面是不是还是数字,所以先放到keepNum,继续往下扫描
                keepNum += ch;

                // 如果此时的ch是表达式最后一个字符,说明扫描完了
                if (index == this.expression.length() - 1) {
   
     
                    numStack.push(Integer.parseInt(keepNum));
                } else {
   
       // index还没走到最后一个
                    // 如果下一个是操作符
                    if (isOper(this.expression.substring(index + 1, index + 2).charAt(0))) {
   
     
                        numStack.push(Integer.parseInt(keepNum));
                        keepNum = "";   // 清空,防止下一轮接着叠加字符
                    }
                }
            }

            // 处理完毕,让index往下走
            index++;

            // index往下走一位如果已经走完,则跳出循环
            if (index >= this.expression.length()) {
   
     
                break;
            }

        }

        // 表达式扫描完毕,则处理两个栈中的数字和操作符
        while (true) {
   
     
            // 如果操作符栈为空,说明数字栈已经是最后的结果
            if (operStack.isEmpty()) {
   
     
                break;
            }
            num2 = numStack.pop();
            num1 = numStack.pop();
            oper = operStack.pop();
            result = cal(num1, oper, num2);
            numStack.push(result);
        }

        return numStack.pop();
    }

    /**
     * 判断字符是不是操作符
     *
     * @return
     */
    private boolean isOper(char ch) {
   
     
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    /**
     * 查询操作符的优先级,数字越大优先级越高
     *
     * @param oper
     * @return
     */
    private int priority(int oper) {
   
     
        if (oper == '*' || oper == '/') {
   
     
            return 1;
        } else if (oper == '+' || oper == '-') {
   
     
            return 0;
        } else {
   
     
            return -1; // 假定目前的表达式只有 +, - , * , /
        }
    }

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

4.2 后缀表达式

4.2.1 逆波兰计算器

逆波兰计算器的原理是使用逆波兰表达式来计算出表达式的值,我们人类能够熟练使用的是中缀表达式,比如2×(9+6/3-5)+4就是一个中缀表达式,但是看到上面的简单计算器就知道处理起来很麻烦。于是有一种逆波兰计算器,计算是在逆波兰表达式(也叫做后缀表达式)的基础上。
逆波兰计算器的计算过程为:从左到右扫描后缀表达式,遇到数字就入栈,遇到操作符就从栈弹出两个数字,然后计算得到的值继续入栈,继续扫描表达式,直到扫描完毕得到结果。
 

4.2.2 中缀转后缀

从逆波兰计算器的扫描过程可以看到,过程特别简单,代码写起来也比较容易。但现在的难点在于:如何把中缀表达式转成后缀表达式?
这个过程已经有大神给出来了,我们记下来即可:
1、初始化两个栈:运算符栈s1和存储中间结果的栈s2;
2、从左到右扫描中缀表达式;
3、遇到操作数时,压入到栈s2;
4、遇到运算符时:
1)如果s1为空或s1栈顶为左括号"(",则压入到s1;
2)不满足1),则和s1栈顶运算符比较优先级,高于,则压入s1;
3)不满足1)和2),弹出s1栈顶运算符并压入到s2,再次回到2)。
5、遇到右括号")“时,依此弹出s1并压入s2,直到遇到左括号”)"为止,此时丢掉一对括号;
6、重复2-5,直到扫描完毕;
7、将s2栈弹出压入到s1,然后s1弹出全部,弹出的顺序即为后缀表达式。

4.2.3 代码实现

package cn.klb.datastructures.stack;

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

/**
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 * @Description:
 * @Date: Create in 2023/4/2 15:24
 * @Modified By:
 */
public class PolandNotation {
   
     
    /**
     * 逆波兰计算器
     * 输入字符串数学表达式,计算出表达式的值
     *
     * @param infixExpressionStr
     * @return
     */
    public int reversePolishCalculator(String infixExpressionStr) {
   
     
        List<String> suffix = infixToSuffix(infixExpressionStr);    // 先把中序表达式转成后序表达式
        Stack<String> stack = new Stack<String>();  // 定义一个栈用于计算
        int num1;   // 存放临时数字
        int num2;   // 存放临时数字
        int result = 0; // 存放计算结果
        String str; // 存放遍历到的字符串
        // 遍历后续表达式执行计算
        for (int index = 0; index < suffix.size(); index++) {
   
     
            str = suffix.get(index);
            if(isOper(str)){
   
         // 如果是操作符,则弹出栈内元素进行计算
                num2 = Integer.parseInt(stack.pop());
                num1 = Integer.parseInt(stack.pop());
                result = cal(num1,str,num2);
                stack.push(String.valueOf(result));
            }else{
   
       // 如果是数字,直接压入栈
                stack.push(str);
            }
        }
        return result;
    }

    /**
     * String类型的中缀表达式转成List类型的后缀表达式的
     *
     * @param InfixExpressionStr
     */
    public List<String> infixToSuffix(String InfixExpressionStr) {
   
     
        List<String> InfixExpressionList = stringToList(InfixExpressionStr);    // String转List
        String str; // 临时存放遍历到的List元素
        List<String> SuffixExpressionList = new ArrayList<String>();    // 存放转换后的结果

        Stack<String> s1 = new Stack<String>(); // 存放运算符
        Stack<String> s2 = new Stack<String>(); // 存放中间结果

        // 从左到右遍历中缀表达式List
        for (int index = 0; index < InfixExpressionList.size(); index++) {
   
     
            str = InfixExpressionList.get(index);   //  当前元素
            if (isOper(str)) {
   
         // 如果扫描到的是运算符
                if (s1.isEmpty() || str.equals("(")) {
   
        // 如果当前s1为空或者str是左括号,则直接压入到s1
                    s1.push(str);
                } else {
   
       // 如果s1已经有运算符
                    if (str.equals(")")) {
   
         // 如果str是右括号
                        // 把s1的操作符全部弹出并压入到s2
                        // 直到遇到左括号
                        // 此时一对括号丢弃掉(s1的左括号丢掉,str的右括号丢掉)
                        while (!"(".equals(s1.peek())) {
   
     
                            s2.push(s1.pop());
                        }
                        s1.pop();   // 丢掉左括号
                    } else {
   
      // 如果str不是右括号
                        // 如果str优先级小于s1的栈顶
                        // 则先将s1栈顶弹出压入到s2
                        // 然后再继续比较str和s1栈顶操作符的优先级,直到比它大为止
                        while (priority(str) <= priority(s1.peek())) {
   
     
                            s2.push(s1.pop());
                            if (s1.isEmpty()) {
   
     
                                break;
                            }
                        }
                        s1.push(str); // 如果str优先级大于s1的栈顶,则直接压入
                    }
                }
            } else {
   
       // 如果扫描到的是操作数,直接压入s2
                s2.push(str);
            }
        }

        // 遍历完成后,把s1的剩余元素一次弹出并压入到s2
        while (!s1.isEmpty()) {
   
     
            s2.push(s1.pop());
        }

        // 把s2元素全部弹出,压入到s1,完成s2的逆序
        while (!s2.isEmpty()) {
   
     
            s1.push(s2.pop());
        }

        // 完成赋值
        while (!s1.isEmpty()) {
   
     
            SuffixExpressionList.add(s1.pop());
        }

        return SuffixExpressionList;
    }

    /**
     * 字符串表达式转成对应的List
     *
     * @param Expression
     * @return
     */
    public List<String> stringToList(String Expression) {
   
     
        List<String> list = new ArrayList<String>();    // 定义一个List存放结果
        String keepNum = "";    // 用于拼接多位的数字
        char c; // 临时存放遍历到的字符

        // 遍历字符串
        for (int index = 0; index < Expression.length(); index++) {
   
     
            c = Expression.charAt(index);
            // 如果 c 是操作符,直接添加到List
            if (isOper(c)) {
   
     
                list.add("" + c);
            } else {
   
     
                keepNum += c;
                // 如果此时index已经扫描到最后一位了
                if (index == Expression.length() - 1) {
   
     
                    list.add(keepNum);
                    keepNum = "";
                } else {
   
     
                    if (isOper(Expression.charAt(index + 1))) {
   
     
                        list.add(keepNum);
                        keepNum = "";
                    }
                }
            }
        }

        // 返回转换好的list
        return list;
    }

    /**
     * 判断字符是不是操作符
     *
     * @return
     */
    private boolean isOper(char ch) {
   
     
        return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')';
    }

    /**
     * 判断字符串是不是操作符
     *
     * @return
     */
    private boolean isOper(String str) {
   
     
        return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str) || "(".equals(str) || ")".equals(str);
    }

    /**
     * 查询操作符的优先级,数字越大优先级越高
     *
     * @param str
     * @return
     */
    private int priority(String str) {
   
     
        char oper = str.charAt(0);
        if (oper == '*' || oper == '/') {
   
     
            return 1;
        } else if (oper == '+' || oper == '-') {
   
     
            return 0;
        } else {
   
     
            return -1; // 假定目前的表达式只有 +, - , * , /
        }
    }

    /**
     * 计算
     *
     * @param num1
     * @param num2
     * @param oper
     * @return
     */
    private int cal(int num1, String oper, int num2) {
   
     
        int res = 0; // res 用于存放计算的结果
        switch (oper.charAt(0)) {
   
     
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num1 - num2;
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num1 / num2;
                break;
            default:
                break;
        }
        return res;
    }
}