第四章 栈结构

4.1 栈的介绍

栈(英语:stack)又称为堆栈或堆叠,是计算机科学中的一种抽象数据类型,只允许在有序的线性数据集合的一端(称为堆栈顶端,英语:top)进行加入数据(英语:push)和移除数据(英语:pop)的运算,因而按照先入后出(FILO-First In Last Out)的原理运作。常与另一种有序的线性数据集合队列相提并论,栈常用一维数组或链表来实现。
数据入栈
数据出栈

4.2 栈的应用场景

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

4.3 栈的数组实现

因为栈是一种有序的线性列表,可以使用数组的结构来模拟栈的使用。

思路分析:

  • 首先定义一个数组 stack 来模拟栈。
  • 定义栈顶指针 top,初始化为 -1。
  • 入栈操作,当有数据压入到栈时,stack[++top] = data。
  • 出栈操作,当有数据出栈时,value = stack[top–]。
public class ArrayStackTest {

    public static void main(String[] args) {
        //创建一个 ArrayStack 对象表示栈
        ArrayStack stack = new ArrayStack(4);
        //控制是否退出菜单
        boolean loop = true;
        String key = "";
        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) {
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~~");
    }
}

class ArrayStack {
    // 栈的大小
    private int maxSize;
    // 数组模拟栈
    private int[] stack;
    // 栈顶指针
    private int top = -1;

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

    public boolean isFull() {
        return top == (maxSize - 1);
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public void push(int value) {
        if (isFull()) {
            System.out.println("栈已满,无法添加数据!");
            return;
        }
        stack[++top] = value;
    }

    public int pop() {
        if (isEmpty()) {
            throw new IllegalArgumentException("栈已空,无法取出数据");
        }
        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]);
        }
    }
}

4.4 栈实现综合计算器

中缀表达式

与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被电脑解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。与前缀或后缀记法不同的是,中缀记法中括号是必需的,计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。

思路分析:

  • 定义 index 值(索引)来遍历表达式。
  • 如果对应的值是一个数字,则直接压入数字栈。
  • 如果对应的值是一个符号,则根据如下规则入符号栈。
    • 如果当前符号栈为空,或者当前操作符的优先级大于栈中的操作符,则直接压入符号栈。
    • 如果当前操作符的优先级小于或等于栈中的操作符,则需要从数字栈中 pop 出两个数据和符号栈 pop 出一个符号进行运算,然后将计算结果压入数字栈,再将当前操作符压入符号栈。
  • 当表达式扫描完毕,就顺序的从数字栈和符号栈中 pop 出响应的数字和符号进行运算。
  • 最后在数字栈中只存在一个数字,就是表达式的结果。

代码实现:

package com.thundersoft.datastructure.stack;


public class Calculator {

    public static void main(String[] args) {
        // 表达式
        String expression = "7*2*2-5+1-5+3-4";
        // 定义 index
        int index = 0;
        // 用于拼接多位数
        String keepNum = "";
        // 创建数字栈
        ArrayStack numberStack = new ArrayStack(10);
        // 创建符号栈
        ArrayStack operStack = new ArrayStack(10);
        // 将所有字符入栈
        while (true) {
            // 获取表达式中的每个字符
            char item = expression.substring(index, index + 1).charAt(0);
            // 判断当前字符是否为符号
            if (isOper(item)) {
                // 如果当前符号栈为空
                if (operStack.isEmpty()) {
                    operStack.push(item);
                } else {
                    // 当前操作符的优先级小于或等于栈中的操作符
                    if (convertOper(item) <= convertOper(operStack.last())) {
                        // 从数字栈中 pop 出两个数据和符号栈 pop 出一个符号进行运算
                        int number1 = numberStack.pop();
                        int number2 = numberStack.pop();
                        int oper = operStack.pop();
                        // 进行运算
                        int result = cal(number1, number2, oper);
                        // 将计算结果压入数字栈
                        numberStack.push(result);
                        // 将当前操作符号要入符号栈
                        operStack.push(item);
                    } else {
                        // 当前操作符的优先级大于栈中的操作符,则直接压入符号栈
                        operStack.push(item);
                    }
                }
            } else {
                // 判断当前字符是否为数字
                keepNum += item;
                //如果 item 已经是 expression 的最后一位,就直接入栈
                if (index == expression.length() - 1) {
                    numberStack.push(Integer.parseInt(keepNum));
                } else {
                    // 判断下一个字符是不是数字,如果是数字就继续扫描,如果是运算符则入栈
                    if (isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        numberStack.push(Integer.parseInt(keepNum));
                        // 将暂存值清空
                        keepNum = "";
                    }
                }
            }
            // 判断是否扫描到 expression 最后
            index++;
            if (index >= expression.length()) {
                break;
            }
        }
        // 当表达式扫描完毕,就顺序的从数栈和符号栈中 pop 出相应的数和符号进行计算
        while (true) {
            if (operStack.isEmpty()) {
                break;
            }
            int number1 = numberStack.pop();
            int number2 = numberStack.pop();
            int oper = operStack.pop();
            // 进行运算
            int result = cal(number1, number2, oper);
            // 将计算结果压入数字栈
            numberStack.push(result);
        }
        System.out.printf("表达式 %s = %d \n", expression, numberStack.pop());
    }

    private static boolean isOper(char item) {
        return item == '+' || item == '-' || item == '*' || item == '/';
    }

    private static int convertOper(int item) {
        if (item == '*' || item == '/') {
            return 1;
        } else if (item == '+' || item == '-') {
            return 0;
        } else {
            return -1;
        }
    }

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

4.5 栈实现逆波兰计算器

后缀表达式

后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则),例如常规中缀记法的 “3 - 4 + 5” 在逆波兰记法中写作 “3 4 - 5 +” 表示:先3减去4,再加上5。

思路分析:

例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 * 6 -,针对后缀表达式求值步骤如下:

  • 从左至右扫描,将 3 和 4 压入堆栈。
  • 遇到 + 运算符弹出 4 和 3,计算出 3+4 的值,然后将结果 7 压入栈。
  • 继续扫描将 5 入栈。
  • 遇到 × 运算符弹出 5 和 7,计算出 7×5 的值,然后将结果 35 压入栈。
  • 继续扫描将 6 入栈。
  • 最后 - 运算符,计算出 35-6 的值,得出最终结果 29。

代码实现:

public class PolandNotation {

    public static void main(String[] args) {
        String suffixExpression = "3 4 + 5 * 6 -";
        String[] split = suffixExpression.split(" ");
        List<String> list = Arrays.asList(split);
        System.out.println(list);
        int result = calculateList(list);
        System.out.println("计算的结果是:" + result);
    }

    private static int calculateList(List<String> list) {
        // 创建栈结构
        Stack<String> stack = new Stack<>();
        for (String item : list) {
            // 使用正则表达式去除整数
            if (item.matches("\\d+")) {
                stack.push(item);
            } else {
                String number1 = stack.pop();
                String number2 = stack.pop();
                int result = calculate(number1, number2, item);
                stack.push(result + "");
            }
        }
        // 最后留在 stack 中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

    private static int calculate(String number1, String number2, String oper) {
        int result;
        int num1 = Integer.parseInt(number1);
        int num2 = Integer.parseInt(number2);
        if (oper.equals("+")) {
            result = num1 + num2;
        } else if (oper.equals("-")) {
            result = num2 - num1;
        } else if (oper.equals("*")) {
            result = num1 * num2;
        } else if (oper.equals("/")) {
            result = num2 / num1;
        } else {
            throw new RuntimeException("运算符有误");
        }
        return result;
    }
}

4.6 中缀表达式转换为后缀表达式

后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。

思路分析:

  • 1.初始化两个栈,运算符栈operStack和存储中间结果的栈numberStack。
  • 2.从左至右扫描整个中缀表达式。
  • 3.当遇到操作数时,将操作数压入到 numberStack。
  • 4.当遇到运算符时,比较其与 operStack 栈顶运算符的优先级。
    • 4.1如果 operStack 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈。
    • 4.2.若当前运算符的优先级比栈顶运算符的高,也将运算符压入 operStack。
    • 4.3若不满足上述条件,则将 operStack 栈顶的运算符弹出并压入到 numberStack 中,再次转到 4.1 步骤中与 operStack 中新的栈顶运算符相比较。
  • 5.遇到括号时,如果是左括号 “(” 时,直接压入 operStack 栈中。
  • 6.遇到括号时,如果是右括号 “)” 时,则依次弹出 operStack 栈顶的运算符并压入 numberStack 栈中,直到遇到左括号为止,此时将这对括号丢弃。
  • 7.重复执行步骤 2 至 6,直到表达式的最右边。
  • 8.将 operStack 栈中剩余的运算符依次弹出并压入 numberStack。
  • 9.依次弹出 numberStack 栈中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。

代码实现

public class InfixToSufixExpressionTest {

    public static void main(String[] args) {
        String param = "122+((2+3)*4)-5";
        List<String> list = toInfixExpressionList(param);
        List<String> expressionList = toSufixExpressionList(list);
        System.err.println(expressionList);
    }

    /**
     * 中缀表达式转后缀表达式
     *
     * @param infixs
     * @return
     */
    private static List<String> toSufixExpressionList(List<String> infixs) {
        List<String> resultList = new ArrayList<>();
        // 初始化两个栈,运算符栈operStack和存储中间结果的栈numberStack。
        Stack<String> operStack = new Stack<>();
        Stack<String> numberStack = new Stack<>();
        // 从左至右扫描整个中缀表达式。
        for (String item : infixs) {
            if (item.matches("\\d+")) {
                // 当遇到操作数时,将操作数压入到 numberStack。
                numberStack.push(item);
            } else if (item.equals("(")) {
                // 如果 operStack 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈。
                operStack.push(item);
            } else if (item.equals(")")) {
                // 如果是右括号 “)” 时,则依次弹出 operStack 栈顶的运算符并压入 numberStack 栈中,直到遇到左括号为止
                while (!operStack.peek().equals("(")) {
                    numberStack.push(operStack.pop());
                }
                // 此时将这对括号丢弃
                operStack.pop();
            } else {
                // 若当前运算符的优先级比栈顶运算符的小或者相等
                while (!operStack.isEmpty() && operationPriority(operStack.peek(), item)) {
                    // 将 operStack 栈顶的运算符弹出并压入到 numberStack 中,再次转到 4.1 步骤中与 operStack 中新的栈顶运算符相比较。
                    numberStack.push(operStack.pop());
                }
                operStack.push(item);
            }
        }
        // 将 operStack 栈中剩余的运算符依次弹出并压入 numberStack。
        while (!operStack.isEmpty()) {
            numberStack.push(operStack.pop());
        }
        // 逆序
        while (!numberStack.isEmpty()) {
            resultList.add(numberStack.pop());
        }
        Collections.reverse(resultList);
        return resultList;
    }

    /**
     * 中缀表达式转集合
     *
     * @param param
     * @return
     */
    private static List<String> toInfixExpressionList(String param) {
        ArrayList<String> list = new ArrayList<>();
        int index = 0;
        String keepNum;
        while (index < param.length()) {
            char ch = param.charAt(index);
            if (isNotNumber(ch)) {
                list.add(ch + "");
                index++;
            } else {
                keepNum = "";
                while (index < param.length() && isNumber(param.charAt(index))) {
                    keepNum += param.charAt(index);
                    index++;
                }
                list.add(keepNum);
            }
        }
        return list;
    }

    private static boolean isNumber(char param) {
        return param >= 48 && param <= 57;
    }

    private static boolean isNotNumber(char param) {
        return param < 48 || param > 57;
    }

    /**
     * 比较运算符的优先级
     *
     * @param operation1
     * @param operation2
     * @return
     */
    private static boolean operationPriority(String operation1, String operation2) {
        return operationValue(operation1) >= operationValue(operation2);
    }

    /**
     * 获取运算符优先级的值
     *
     * @param operation
     * @return
     */
    private static int operationValue(String operation) {
        int result = 0;
        switch (operation) {
            case "+":
                result = 1;
                break;
            case "-":
                result = 1;
                break;
            case "*":
                result = 2;
                break;
            case "/":
                result = 2;
                break;
            default:
                result = 0;
                break;
        }
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值