数据结构和算法学习第三节 栈

概述

    1、栈(stack)是一个先入后出的有序列表
    2、栈(stack)限制线性表中元素的插入和删除,只能在线性表的同一端进行操作的一种特殊线性表,允许插入和删除的一端是变化的一端,称为栈顶(Top),另一端是固定的一端称为栈底(Bottom);
    3、新增元素:最先放入栈的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
    入栈示意图
在这里插入图片描述
    元素出栈示意图
在这里插入图片描述
    应用场景简要介绍
    1、子程序的调用:在跳往子程序前,会先将下个指令的地址存入到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
    2、处理递归调用:和子程序类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入到堆栈中。
    3、二叉树的遍历。
    4、表达式的转换与求值(中缀表达式转后缀表达式)。
    5、图的深度优先搜索法。

栈的代码实现(使用数组实现)

    数组实现栈思路分析(先不考虑线程安全问题)
    1、定义一个top表示栈顶,初始化为-1。
    2、入栈的操作,当有数据入栈时, top ++ ,stack[top] = data
    3、出栈的操作:临时变量 value = stack[top];top–;return value;

package com.example.data.sparse.stack;

/**
 * @author zjt
 * 数组模拟栈
 */
public class ArrayStack<E> {

    private int maxSize; //表示栈的最大空间

    private Object[] stack; // 数组模拟栈,数据放在数组中

    private int top = -1; // 表示栈顶 初始化为-1

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

    // 判断栈是否已经满了
    public boolean isFull() {
        // 数组下标从0 开始
        return top >= (maxSize - 1);
    }

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

    // 入栈
    public void push(E e) {
        // 判断栈是否已经满了
        if (isFull()) {
            System.out.println("栈已经满了");
            return;
        }
        top++;
        stack[top] = e;
    }

    // 出栈
    public E pop() {
        // 判断栈是否为空
        if (isEmpty()) {
            throw new RuntimeException("空栈");
        }
        Object value = stack[top];
        top--;
        return (E) value;
    }

    // 遍历栈 遍历时需要从栈顶开始显示数据
    public void show() {
        if (isEmpty()) {
            System.out.println("空栈");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.println(i + "\t" + stack[i]);
        }
    }

}

class Client {
    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>(5);
        stack.push(10);
        stack.push(20);
        stack.push(30);
        stack.push(40);
        stack.push(50);
        stack.push(60);
        stack.show();
        System.out.println("出栈");
        stack.pop();
        stack.show();
        stack.pop();
        stack.pop();
        stack.pop();
        stack.pop();
        System.out.println("出栈");
        stack.push(60);
        stack.show();

    }
}

    栈也可以使用链表方式实现,使用头插法、头删法使用会简单一些。

案例

    案例一 栈实现表达式计算:比如1+2+3*4-5的值。
    思路
    1、我们需要两个栈,一个是数栈,用来存放数字。一个是符号栈,用来存放运算符。
    2、通过index索引,来遍历我们的表达式。
    3、如果发现是数字就直接存放到数栈。
    4、如果发现扫描到一个符号,就要分情况讨论:
        4.1如果当前符号栈为空,就直接入栈。
        4.2如果符号栈有操作符,就进行比较,如果当前操作符号的优先级小于或者等于栈中的操作符号,就需要从数栈中pop出两个数字,然后从符号栈中pop出一个符号进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈。如果当前的操作符号的优先级大于栈中的操作符号,就直接入栈。
    5、当表达式扫描完毕后,就顺序的从数栈和符号栈pop出相应的数和符号并运行。
    6、最后在数栈只有一个数字,就是表达式的结果。
    栈中的扩展功能

    // 查看当前栈顶的值,但是不出栈,只是偷看一眼
    public E peek() {
        if (isEmpty()) {
            throw new RuntimeException("空栈");
        }
        return (E) stack[top];
    }

    // 扩展功能 返回运算符的优先级
    // 这里定义 数字越大 优先级越高
    public int priority(char opera) {
        switch (opera) {
            case '*':
            case '/':
                return 1;
            case '+':
            case '-':
                return 0;
            default:
                return -1; // 假定表达式仅有 者四个运算符号 返回 -1 表示符号有问题

        }
    }

    // 判断是否是操作符号
    public boolean isOpera(char c) {
        return oprList.contains(c);
    }

    // 计算方法
    public int cal(int num1, int num2, char opera) {
        switch (opera) {
            case '+':
                return num1 + num2;
            case '-':
                return num2 - num1; // 这里需要注意顺序,原因是:先入栈的后出栈 对应 减数与被减数。除法也一样
            case '*':
                return num1 * num2;
            case '/':
                return num2 / num1;
            default:
                throw new RuntimeException("操作符号有误");
        }
    }

    // 计算方法
    public int cal1(int num1, int num2, char opera) {
        switch (opera) {
            case '+':
                return num1 + num2;
            case '-':
                return num1 - num2;
            case '*':
                return num1 * num2;
            case '/':
                return num2 / num1;
            default:
                throw new RuntimeException("操作符号有误");
        }
    }

    计算逻辑

// 栈之前已经实现,直接使用 扩展一下功能
class Calculator {

    public static void main(String[] args) {
        // 表达式
        String expression = "7-3+4*3/3-2-1-1";
        // 书栈
        ArrayStack<Integer> numStack = new ArrayStack<>(10);
        // 符号栈
        ArrayStack<Character> operaStack = new ArrayStack<>(10);
        // 定义变量 index用于扫描 其它是临时变量
        int index = 0, num1 = 0, num2 = 0, result = 0;
        char opera; // 运算符
        char ch = ' ';// 将每次扫描得到的char 保存到临时变量中
        // 开始扫描
        do {
            // 依次得到表达式中每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            // 判断ch是什么然后做相应的处理
            if (operaStack.isOpera(ch)) {
                if (operaStack.isEmpty()) { // 符号栈是空栈
                    operaStack.push(ch);
                } else {
                    // 如果符号栈有操作符,就进行比较,如果当前操作符号的优先级小于或者等于栈中的操作符号,
                    // 就需要从数栈中pop出两个数字,然后从符号栈中pop出一个符号进行运算,将得到的结果入数栈,
                    // 然后将当前的操作符入符号栈。
                    // 如果当前的操作符号的优先级大于栈中的操作符号,就直接入栈
                    if (operaStack.priority(ch) <= operaStack.priority(operaStack.peek())) {
                        //优先级小于或者等于栈中的操作符号
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        opera = operaStack.pop();
                        result = numStack.cal(num1, num2, opera);
                        // 将得到的结果入数栈
                        numStack.push(result);
                        operaStack.push(ch);
                    } else {
                        operaStack.push(ch);
                    }
                }
            } else {
                // 如果直接是数字 就直接入栈
                numStack.push(Integer.parseInt(String.valueOf(ch)));
            }
            index++;
        } while (index < expression.length());
        // 当表达式扫描完毕后,获取到两个栈 栈中的计算与真实计算刚好相反 原因是 后入栈的先出栈导致反着计算
        // 所以会出现 1-1*1+9 = -9 的情况 这时 数栈中存储 1 1 9 符号栈中存储 - + 按照栈的规则计算是 1+9  1-10 的错误
        // 数栈
//        ArrayStack<Integer> numStack1 = new ArrayStack<>(10);
//        // 符号栈
//        ArrayStack<Character> operaStack1 = new ArrayStack<>(10);
        // 进行栈的反序列
//        for (;;){
//            if (operaStack.isEmpty()) {
//                break;
//            }
//            operaStack1.push(operaStack.pop());
//        }
//        for (;;){
//            if (numStack.isEmpty()) {
//                break;
//            }
//            numStack1.push(numStack.pop());
//        }

        while (true) {
            // 如果符号栈为空,则计算得到最后的结果
            if (operaStack.isEmpty()) {
                System.out.println(String.format("表达式 %s  = %d", expression, numStack.pop()));
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            opera = operaStack.pop();
            result = numStack.cal1(num1, num2, opera);
            numStack.push(result);
        }


    }

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值