【DS】 Stack 基本用法

在这里插入图片描述

当年面试问的最多的那批 Java 集合框架现在问的还多么? 编程十年(手动狗头) , 我把这些集合框架做成盲盒, 无论你多难, 我都想去了解你, 今天来看-----pia~, Stack,一种线性数据结构,遵循后进先出(LIFO)原则。支持push(压入)、pop(弹出)和 peek(查看栈顶)等操作,常用于处理方法调用、表达式求值等场景。

一. 概念

: 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈。出数据在栈顶。

在这里插入图片描述
栈在现实生活中的例子:往弹夹里面装子弹是入栈, 开枪发射时是出栈

二. 栈的使用

在这里插入图片描述

    public static void main(String[] args) {
        Stack<Integer> s = new Stack();
        s.push(1);
        s.push(2);
        s.push(3);
        s.push(4);
        System.out.println(s.size()); // 获取栈中有效元素个数---> 4
        System.out.println(s.peek()); // 获取栈顶元素---> 4
        s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
        System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
        if(s.empty()){
            System.out.println("栈空");
        }else{
            System.out.println(s.size());
        }
    }

三. 栈的模拟实现

在这里插入图片描述
从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。

class MyStack {
    int[] array;
    int size;
    public MyStack(){
        array = new int[3];
    }
    public int push(int e){
        ensureCapacity();
        array[size++] = e;
        return e;
    }
    public int pop(){
        int e = peek();
        size--;
        return e;
    }
    public int peek(){
        if(empty()){
            throw new RuntimeException("栈为空,无法获取栈顶元素");
        }
        return array[size-1];
    }
    public int size(){
        return size;
    }
    public boolean empty(){
        return 0 == size;
    }
    private void ensureCapacity(){
        if(size == array.length){
            array = Arrays.copyOf(array, size*2);
        }
    }
}

如果要用单链表实现栈的话, 需要头插, 头删
如果用双向链表的话, 头插, 头删, 尾插, 尾删时间复杂度都是 O (1)
所以 LinkedList 可以当作 Stack 来使用

四. 栈的应用场景

1. 改变元素的序列

  1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
    A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1

答案: C, 注意不是非得所有元素都进入之后才能出, 是随时可以出栈

  1. 一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
    A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA

答案: B

2. 将递归转化为循环

比如:逆序打印链表 (还有二叉树的前中后序遍历、快速排序等等的非递归方式都用到栈)

	// 递归方式
    void printList(Node head){
        if(null != head){
            printList(head.next);
            System.out.print(head.val + " ");
        }
    }
    // 循环方式
    void printList(Node head){
        if(null == head){
            return;
        }
        Stack<Node> s = new Stack<>();
        // 将链表中的结点保存在栈中
        Node cur = head;
        while(null != cur){
            s.push(cur);
            cur = cur.next;
        }
        // 将栈中的元素出栈
        while(!s.empty()){
            System.out.print(s.pop().val + " ");
        }
    }

3. 括号匹配

OJ链接

思路:
遇到左括号入栈, 遇到右括号时将栈顶的对应左括号出栈, 如果两个括号不是一对的话就说明匹配不上

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack=new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch=s.charAt(i);
            if(ch=='['||ch=='('||ch=='{'){
                stack.push(ch);
            }else{
                if(stack.isEmpty()){
                    return false;
                }
                int flag=stack.pop();
                if(!(flag=='['&&ch==']' 
                        ||flag=='('&&ch==')'
                        ||flag=='{'&&ch=='}')){
                    return false;
                }
            }
        }
        
        return stack.isEmpty();
    }
}

4. 逆波兰表达式求值

OJ链接

逆波兰表达式又叫后缀表达式, 我们一般使用的是中缀表达式, 后缀表达式是将 运算符号写在两个操作数的后面
比如: ((2 + 1) * 3) --> ((2 1 +) 3 *)

思路:
遇到数字就入栈, 遇到操作符就将栈中最上面的两个数字取出来根据运算符进行运算, 并将结果压入栈中

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack=new Stack<>();
        for(int i=0;i<tokens.length;i++){
            String str=tokens[i];
            if(str.equals("+")){
                int right=stack.pop();
                int left=stack.pop();
                stack.push(left+right);
            }else if(str.equals("-")){
                int right=stack.pop();
                int left=stack.pop();
                stack.push(left-right);
            }else if(str.equals("*")){
                int right=stack.pop();
                int left=stack.pop();
                stack.push(left*right);
            }else if(str.equals("/")){
                int right=stack.pop();
                int left=stack.pop();
                stack.push(left/right);
            }else {
                //是数字
                stack.push(Integer.valueOf(str));
            }
        }
        return stack.pop();
    }
}

5. 出栈入栈次序匹配

OJ链接

思路:
一个指针指向出栈顺序数组, 先根据入栈顺序往里面压, 如果栈顶的元素与出栈
指针指向的值相同, 就出栈, 指针++, 直到栈顶元素与指针指向的值不相等, 此时继续入栈, 重复上次操作, 如果最后栈为空, 说明匹配上了, 否则说明匹配不上

import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack = new Stack<>();
        int pop = 0;
        for (int x : pushA) {
            stack.push(x);
            while (!stack.isEmpty() && stack.peek() == popA[pop]) {
                stack.pop();
                pop++;
            }
        }
        return stack.isEmpty();
    }
}

6. 最小栈

OJ链接

思路:
使用两个栈, 一个是普通栈, 用来压入正常值, 一个是单调递减栈, 也就是说栈中元素从下往上, 值是递减的, 压入的规则是, 如果要压入的正常值, 比单调栈顶值小, 就压入正常值, 否则就压入单调栈顶值, 普通栈的话压入正常值就可以了, 出栈的时候, 两个栈同时 pop

class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;

    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int x) {
        xStack.push(x);
        minStack.push(Math.min(minStack.peek(), x));
    }
    
    public void pop() {
        xStack.pop();
        minStack.pop();
    }
    
    public int top() {
        return xStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

五. 概念区分

栈、虚拟机栈、栈帧有什么区别呢?

  1. 栈(Stack):
    一种线性数据结构,遵循后进先出(Last In, First Out,LIFO)的原则。支持两种主要操作:入栈(Push)和出栈(Pop)。数据项只能从栈的顶部进行插入和删除。

  2. 虚拟机栈(Virtual Machine Stack):
    虚拟机栈是在JVM 中的一块内存空间。每个运行的线程都会有一个独立的虚拟机栈。虚拟机栈中存储了方法的调用信息,包括方法的参数、局部变量和返回值等。每个方法在虚拟机栈上都会对应一个栈帧。

  3. 栈帧(Stack Frame):
    栈帧是与函数调用相关的概念,它是虚拟机栈中的一个单元,包含了一个方法的调用信息和局部数据。当一个函数被调用时,会创建一个对应的栈帧并推入虚拟机栈,当函数执行完毕后,栈帧会被弹出。
    栈帧通常包括以下几个部分:

  • 方法参数:存储被调用方法的参数值。
  • 方法返回地址:指向调用该方法的指令地址,用于在方法执行完毕后返回到正确的位置继续执行。
  • 局部变量区:用于存储函数内部定义的局部变量。
  • 操作数栈(Operand Stack):用于存储函数执行过程中的操作数和临时结果。
  • 动态链接(Dynamic Link):指向调用该方法的上一个方法的栈帧,用于访问外部的变量和数据。

以上就是对 栈的讲解, 希望能帮到你 !
评论区欢迎指正 !

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值