04-数据结构与算法-栈

4、栈

4.1 概念介绍

栈:stack,一种先进后出(first in last out FILO)的有序列表; 元素的插入和删除只能在线性表的同一端进行,允许进行插入和删除操作的一端被称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。插入元素一般被称为入栈(push),删除元素一般被称为出栈(pop)。

元素入栈示意图:
在这里插入图片描述

元素出栈:

在这里插入图片描述

4.2 数组模拟栈

  1. **思路分析:**数组模拟栈,需要定义数组大小,指向栈顶的指针top,初始化为-1;

入栈:先判断栈是否满,栈顶指针++,stack[top] = value

出栈:先判断栈是否空,临时变量保存栈顶元素,栈顶指针top–;返回栈顶元素

  1. 代码实现:
package com.kevin.arrayStack;

/**
 * @author : kevin ding
 * @date : 2022/2/27 10:42
 * @description : 用数组模拟栈
 */
public class ArrayStack {
    private int maxSize;
    private int top;
    private int[] arrayStack;

    // 构造器
    public ArrayStack(int maxSize){
        // 初始化容量
        this.maxSize = maxSize;
        this.top = -1;
        // 初始化容量为maxSize大小的容数组
        this.arrayStack = new int[this.maxSize];
    }

    // 判断栈是否为空  当top值为初始化的值时,栈为空
    public boolean isEmpty(){
        return this.top == -1;
    }

    // 判断栈是否满: 当top指向栈顶时,top == maxSize-1 时,栈满
    public boolean isFull(){
        return this.top == this.maxSize-1;
    }

    // 入栈
    public void push(int value){
        // 首先判断是否栈满
        if(isFull()){
            System.out.println("栈满,无法将元素入栈...");
            return;
        }
        // 进行入栈操作:
        top += 1;
        arrayStack[top] = value;
    }

    // 出栈
    public int pop(){
        // 首先判断栈是否为空
        if(isEmpty()){
            throw new RuntimeException("栈为空,没有元素可出栈...");
        }
        // 元素出栈
        int curValue = arrayStack[top];
        top -= 1;
        return curValue;
    }

    // 显示栈顶元素
    public int peek(){
        // 首先判断栈是否为空
        if(isEmpty()){
            throw new RuntimeException("栈为空,栈顶没有元素...");
        }
        return arrayStack[top];
    }

    // 遍历栈的元素
    public void  showAll(){
        // 判断栈是否为空
        if(isEmpty()){
            System.out.println("栈为空...");
            return;
        }
        // 从栈顶开始遍历所有元素
        for(int i = top; i>=0; i--){
            System.out.println("arrayStack[" + i + "]=" + arrayStack[i]);
        }
    }
}
  1. 验证:
package com.kevin.arrayStack;

import java.util.Scanner;

/**
 * @author : kevin ding
 * @date : 2022/2/27 11:03
 * @description :   数组模拟栈的测试类
 */
public class ArrayStackTestDemo {
    public static void main(String[] args) {
        // 创建一个ArrayStack对象表示栈
        ArrayStack arrayStack = new ArrayStack(5);
        // 通过界面操作来验证功能的实现
        String choose = "";
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);

        // 进行循环验证
        while (loop){
            // 显示控制台提示信息
            System.out.println("pop: 将元素入栈");
            System.out.println("push: 栈顶元素出栈");
            System.out.println("show: 遍历栈中所有的元素");
            System.out.println("peek: 显示栈顶的元素(不出栈)");
            System.out.println("exit: 退出程序");

            System.out.println("请输入要选择的操作:");
            choose = scanner.next();
            switch (choose){
                case "pop":
                    try {
                        int value = arrayStack.pop();
                        System.out.println("出栈的元素是:" + value);
                    }catch (Exception exception){
                        exception.printStackTrace();
                    }
                    break;

                case "push":
                    System.out.println("请输入要入栈的元素:");
                    int value = scanner.nextInt();
                    arrayStack.push(value);
                    break;

                case "show":
                    arrayStack.showAll();
                    break;

                case "peek":
                    try {
                        int peekValue = arrayStack.peek();
                        System.out.println("栈顶元素是:" + peekValue);
                    }catch (Exception exception){
                        exception.printStackTrace();
                    }
                    break;

                case "exit":
                    scanner.close();
                    loop = false;
                    break;

                default:
                    System.out.println("选择指令输入有误...");
                    break;
            }
        }

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

4.3 栈的应用

4.3.1 中缀表达式计算器

使用栈来完成中缀表达式的计算:

假设输入表达式:2*7-4+20-62/2-5 的值

思路分析:

  • 定义索引指针,来遍历表达式中的所有元素,并初始化两个栈用于存放遍历到的数字和运算符号

  • 如果遍历到的元素是数字,直接进入存放数字的栈

  • 如果遍历到的元素是符号,分如下情况:

    • 符号栈为空:直接将符号入栈

    • 符号栈不为空

      • 当前符号优先级小于等于栈顶符号优先级:

        从数栈中取出两个栈顶的元素,符号栈中取出一个元素,进行运算,得到的结果存放到数栈中,随后将当前的符号入栈

      • 当前符号优先级大于栈顶符号优先级:直接入栈

  • 表达式扫描完毕,顺序的从数栈和符号栈中弹出元素进行运算,并将运算结果存入到数栈中;

  • 直到最后,符号栈为空,在数栈中只有一个元素,即为表达式的运算结果

代码实现:

  1. 创建一个栈:
package com.kevin.arrayStack;

/**
 * @author : kevin ding
 * @date : 2022/2/27 13:30
 * @description :   中缀表达式计算器专用栈
 */
public class CalculatorStack {
    private int maxSize;
    private int top;
    private int[] arrayStack;

    // 构造器
    public CalculatorStack(int maxSize){
        // 初始化容量
        this.maxSize = maxSize;
        this.top = -1;
        // 初始化容量为maxSize大小的容数组
        this.arrayStack = new int[this.maxSize];
    }

    // 判断栈是否为空  当top值为初始化的值时,栈为空
    public boolean isEmpty(){
        return this.top == -1;
    }

    // 判断栈是否满: 当top指向栈顶时,top == maxSize-1 时,栈满
    public boolean isFull(){
        return this.top == this.maxSize-1;
    }

    // 入栈
    public void push(int value){
        // 首先判断是否栈满
        if(isFull()){
            System.out.println("栈满,无法将元素入栈...");
            return;
        }
        // 进行入栈操作:
        top += 1;
        arrayStack[top] = value;
    }

    // 出栈
    public int pop(){
        // 首先判断栈是否为空
        if(isEmpty()){
            throw new RuntimeException("栈为空,没有元素可出栈...");
        }
        // 元素出栈
        int curValue = arrayStack[top];
        top -= 1;
        return curValue;
    }

    // 显示栈顶元素
    public int peek(){
        // 首先判断栈是否为空
        if(isEmpty()){
            throw new RuntimeException("栈为空,栈顶没有元素...");
        }
        return arrayStack[top];
    }

    // 遍历栈的元素
    public void  showAll(){
        // 判断栈是否为空
        if(isEmpty()){
            System.out.println("栈为空...");
            return;
        }
        // 从栈顶开始遍历所有元素
        for(int i = top; i>=0; i--){
            System.out.println("arrayStack[" + i + "]=" + arrayStack[i]);
        }
    }

    // 判断给定元素的优先级方法
    // 优先级高的 数字越大
    public int getPriority(int operator){
        if(operator == '*' || operator == '/'){
            return 1;
        }else if(operator == '+' || operator == '-'){
            return 0;
        }else{
            // 假定 目前操作符只有 + - * /
            return -1;
        }
    }

    // 判断给定的元素是否为操作符:
    public boolean isOperator(char ch){
        // 给定字符为+ - * / 的任一操作符,则返回true
        return (ch=='+' || ch=='-' || ch=='*' || ch=='/');
    }

    // 对于给定的运算符和参数 进行运算
    // 先出栈的数被num2接收,后出栈的数被num1接收,所以在进行操作时 为num1-num2  num1/num2
    public int calculate(int num1, int num2, int operator){
        int res = 0;    // 用于存放计算结果
        if(operator == '+'){
            res = num1 + num2;
        }else if (operator == '-'){
            res = num1 - num2;
        }else if (operator == '*'){
            res = num1 * num2;
        }else if(operator == '/'){
            res = num1 / num2;
        }
        return res;
    }

}
  1. 计算器实现:
package com.kevin.arrayStack;


/**
 * @author : kevin ding
 * @date : 2022/2/27 14:02
 * @description : 使用栈来实现一个综合计算器(中缀表达式)
 *              计算 7*2+8/4+6-5*3
 *              使用栈完成计算的思路:
 *                  1. 使用一个指针 来遍历表达式
 *                  2. 如果当前指向的是数字,直接入数字的栈
 *                  3. 如果当前指向的是运算符:运算符的栈为空,直接入栈;如果不为空:若当前运算符优先级较高,则直接入栈,
 *                  若当前运算符优先级小于等于栈顶运算符的优先级,则将数栈中pop出两个,运算符栈pop出一个,进行运算,运算结果在push到数栈
 *                  4.当表达式扫描完毕,就顺序的从数栈和符号栈中pop相应的元素进行运算,
 *                  5. 最后在数栈中只有一个数字,即为表达式的结果
 *
 */
public class ArrayStackCalculator {
    public static void main(String[] args) {
        String expression = "7*2+8/4+60+22-5*3";
        // 1.准备工作 :创建两个栈,一个是数栈,一个是运算符栈;指针扫描,临时变量存储数栈pop的元素和操作符,计算结果
        CalculatorStack numStack = new CalculatorStack(10);
        CalculatorStack operatorStack = new CalculatorStack(10);
        int index = 0;
        int num1 = 0;
        int num2 = 0;
        int operator = 0;
        int res = 0;
        char curChar= ' ';  // 每次将扫描到的结果保存到ch中
        String nums = ""; // 用于对多位数进行处理

        // 2.循环扫描表达式 获取表达式中的每个字符
        while(true){
            curChar = expression.charAt(index);
            // 判断这个当前字符是什么
            // 如果是运算符
            if(operatorStack.isOperator(curChar)){
                // 如果运算符的栈不为空,进行优先级的比较
                if(! operatorStack.isEmpty()){
                    // 进行优先级的比较:
                    if(operatorStack.getPriority(curChar) <= operatorStack.getPriority(operatorStack.peek())){
                        num2 = numStack.pop();
                        num1 = numStack.pop();
                        operator = operatorStack.pop();
                        res = numStack.calculate(num1, num2, operator);
                        // 将计算结果入数栈
                        numStack.push(res);
                        // 随后将当前的操作符如栈
                        operatorStack.push(curChar);
                    }else {
                        // 当前的优先级较高,则直接入栈
                        operatorStack.push(curChar);
                    }

                }else{
                    // 运算符的栈为空 当前运算符直接入栈
                    operatorStack.push(curChar);
                }

            }else{
                // 当前为数字 直接入数栈 入栈时由于是字符类型,需要转换成数对应的ASCII码,从字符1转换成数字的1入栈
                // numStack.push(curChar-48);

                // 不能当前扫描的是数字就进栈,有可能是个多位数,需要接着往后判断
                // 需要再往index后面看一位,直到发现后面是符号,才将当前的数入栈
                nums += curChar;
                // 判断 如果curChar已经是最后一位,直接将nums入栈
                if (index == expression.length()-1){
                    numStack.push(Integer.parseInt(nums));
                }else {
                    // 判断index的下一位是数字还是符号,是数字就继续下一轮的扫描不如栈,如果是符号就将nums入栈
                    if(operatorStack.isOperator(expression.charAt(index+1))){
                        numStack.push(Integer.parseInt(nums));
                        // 入栈之后 将num清空,方便下一轮的运算
                        nums = "";
                    }
                }
            }

            // 指针后移
            index ++;
            if(index >= expression.length()){
                break;
            }
        }
        // 当表达式扫描完毕,就顺序的从数栈和符号栈中pop相应的数和符号进行运算
        while(true){
            //如果符号栈为空,计算到最后的结果
            if(operatorStack.isEmpty()){
                break;
            }
            num2 = numStack.pop();
            num1 = numStack.pop();
            operator = operatorStack.pop();
            res = numStack.calculate(num1, num2, operator);
            numStack.push(res);

        }
        // 最后数栈中的数即为结果
        int result = numStack.pop();
        System.out.println("计算结果为:" + result);

    }
}
4.3.2 逆波兰计算器

逆波兰表达式(后缀表达式):3 4 * 5 + 12 -
在这里插入图片描述

思路分析:

  • 定义指针从左到右开始扫描表达式,当前元素是数字,将其进行入栈
  • 当前元素是运算符,从栈中取出两个数字进行运算,并将运算结果入栈
  • 直到表达式遍历结束,栈中仅有一个元素,即为最终的运算结果

代码实现:

package com.kevin.arrayStack;

import com.sun.corba.se.spi.ior.IORFactories;

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

/**
 * @author : kevin ding
 * @date : 2022/2/27 14:44
 * @description :   逆波兰表达式运算器
 */
public class PolandExpressionCalculator {
    public static void main(String[] args) {
        String expression = "18 5 * 24 - 8 +";
        // 对于字符串进行遍历不方便,将其元素存放于数组中进行遍历
        List<String> expressionList = expressionToList(expression);

        // 调用计算器方法
        int res = polandCalculator(expressionList);
        System.out.println("运算结果为:" + res);
    }

    private static int polandCalculator(List<String> expressionList) {
        // 首先创建栈 用于存放遍历到的数字
        Stack<String> stack = new Stack<>();

        // 遍历数组
        for (String s : expressionList) {
            // 若当前元素是数字,直接入栈
            if(s.matches("\\d+")){
                stack.push(s);
            }else{
                // 否则弹出两个元素进行运算,需要用后弹出的进行运算
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if(s.equals("+")){
                    res = num1 + num2;
                }else if (s.equals("-")){
                    res = num1 - num2;
                }else if(s.equals("*")){
                    res = num1 * num2;
                }else if(s.equals("/")){
                    res = num1 / num2;
                }else {
                    throw new RuntimeException("运算符有误,请检查表达式...");
                }
                // 将计算结果入栈
                stack.push("" + res);
            }

        }
        // 最后,栈中的元素即为运算结果
        return Integer.parseInt(stack.pop());
    }

    private static List<String> expressionToList(String expression) {
        String[] split = expression.split(" ");
        List<String> list = new ArrayList<>();
        for (String s : split) {
            list.add(s);
        }
        return list;
    }
}

4.3.3 中缀转换成后缀

上述中,中缀表达式为人类更直观能够看明白的表达式,而后缀表达式为机器更为简单计算的表达式,如何完成从中缀表达式向后缀表达式的转换?逆波兰表达式流程:
在这里插入图片描述

思路分析:

  • 初始化两个栈:运算符栈stack1和储存中间结果的栈stack2

  • 定义指针变量,从左至右扫描中缀表达式;

  • 当遇到操作数时,将其压数栈stack2

  • 当遇到运算符时,比较当前运算符与stack1栈顶运算符的优先级:

    • 如果stack1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    • 否则,若优先级比栈顶运算符的高,也将运算符压入stack1
    • 否则,将stack1栈顶的运算符弹出并压入到stack2中,再次重复这一步骤,与stack1中新的栈顶运算符相比较;
  • 遇到括号时:

    • 如果是左括号“(”,则直接压入stack1
    • 如果是右括号“)”,则依次弹出stack1栈顶的运算符,并压入stack2,直到遇到左括号为止,此时将这一对括号丢弃
  • 重复步骤2至5,直到指针指向表达式的最右边

  • stack1中剩余的运算符依次弹出并压入stack2

  • 依次弹出stack2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

代码实现:

package com.kevin.arrayStack;


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

/**
 * @author : kevin ding
 * @date : 2022/2/27 15:28
 * @description : 中缀表达式转后缀表达式
 */
public class InfixToSuffixExpression {
    public static void main(String[] args) {
        String expression = "7*2+8/4+(60+(22-5))*3";
        // String expression = "1+((2+3)*4)-5";
        // 转换成list方便进行遍历
        List<String> expressionList = expressionToList(expression);

        List<String> suffixExpression = parseToSuffixExpression(expressionList);
        System.out.println("后缀表达式为:" + suffixExpression);
        int res = PolandExpressionCalculator.polandCalculator(suffixExpression);
        System.out.println("表达式的计算结果为:" + res);

    }

    private static List<String> parseToSuffixExpression(List<String> expressionList) {
        // 定义两个栈 stack1用于存放运算符,stack2用于存放中间结果
        Stack<String> stack1 = new Stack<>();
        List<String> stack2 = new ArrayList<>();
        
        // 遍历expressionList
        for (String item : expressionList) {
            // 如果当前值是一个数:直接入栈stack2
            if(item.matches("\\d+")){
                stack2.add(item);
            // 如果遇到的是左括号。直接入栈符号栈stack1
            }else if(item.equals("(")){
                stack1.push(item);
            // 如果是右括号,需要将符号栈中的元素弹出并入stack2,直到stack1的栈顶元素为左括号
            }else if(item.equals(")")){
                while (!stack1.peek().equals("(")){
                    // 不为左括号,就挨个弹出并加入到stack2中
                    stack2.add(stack1.pop());
                }
                // 当前stack1的栈顶元素左括号,直接将其弹出,舍弃
                stack1.pop();
            // 当前是运算符+ - * /
            }else{
                while(true){
                    // 如果符号栈为空,或者栈顶元素为左括号,则当前运算符直接入栈
                    if(stack1.size() == 0 || stack1.peek().equals("(")){
                        stack1.push(item);
                        break;
                    // 否则 进行优先级的比较 当前元素优先级比较高,就直接入栈,当前元素优先级小于等于栈顶元素,将栈顶元素取出并入栈2,再进行循环
                    }else{
                        // 当前优先级小于等于栈顶元素 将stack1中的元素出栈,并加入到stack2中
                        if(getPriority(item) <=  getPriority(stack1.peek())){
                            stack2.add(stack1.pop());
                        }else{
                            stack1.push(item);
                            break;
                        }
                    }
                }
            }
        }
        // 遍历结束之后,将stack1中的元素分别弹出 并加入到stack2中
        while (stack1.size() != 0){
            stack2.add(stack1.pop());
        }
        return stack2;
    }

    private static List<String> expressionToList(String expression) {
        List<String> list = new ArrayList<>();
        // 指定一个指针,开始遍历
        int index = 0;
        String str; // 用于拼接
        do {
            // 如果当前元素是一个非数字,加入到list中
            if(expression.charAt(index) < 48 || expression.charAt(index) > 57){
                list.add(""+ expression.charAt(index));
                index += 1;
            }else{
                // curChar是一个数,需要考虑多位的情况
                str = "";
                while(index < expression.length() && expression.charAt(index) >= 48 && expression.charAt(index)<=57){
                    // 拼接
                    str += expression.charAt(index);
                    index += 1;
                }
                list.add(str);
            }
        }while (index < expression.length());
        return list;
    }

    // 判断给定元素的优先级方法
    // 优先级高的 数字越大
    public static int getPriority(String operator){
        if(operator.equals("*") || operator.equals("/")){
            return 1;
        }else if(operator.equals("+") || operator.equals("-")){
            return 0;
        }else{
            // 假定 目前操作符只有 + - * /
            return -1;
        }
    }

}

验证:

String expression = "7*2+8/4+(60+(22-5))*3";
// String expression = "1+((2+3)*4)-5";
// 转换成list方便进行遍历
List<String> expressionList = expressionToList(expression);

List<String> suffixExpression = parseToSuffixExpression(expressionList);
System.out.println("后缀表达式为:" + suffixExpression);
int res = PolandExpressionCalculator.polandCalculator(suffixExpression);
System.out.println("表达式的计算结果为:" + res);

最后输出结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值