数据结构----栈的概念、模拟实现、栈的使用、栈的应用、有关栈的算法题

1. 栈(Stack) 的概念

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

【后进先出LIFO原则】:
在这里插入图片描述

压栈(Push()):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈(Pop()):栈的删除操作叫做出栈。出数据在栈顶

在这里插入图片描述
栈在现实生活中的例子:
在这里插入图片描述

2. 栈的模拟实现

在这里插入图片描述

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

public 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);
       }
   }
}

3. 栈的使用

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空
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());
   }
}

4. 栈的应用场景

4.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,C选项中先出的元素为3,说明之前入栈的元素为1、2、3,3出栈之后,出栈顺序一定是2在1之前,2不出来,1不可能先出来。

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

4.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 + " ");
   }
}

4.3. 150.逆波兰表达式求值

150.逆波兰表达式求值题目链接
逆波兰表达式介绍

逆波兰表达式又叫做后缀表达式。

  • 表达式一般由操作数(Operand)、运算符(Operator)组成。
  • 算术表达式中,通常把运算符放在两个操作数的中间,这称为中缀表达式(InfixExpression),如A+B。
  • 把运算符写在操作数之前,称为波兰表达式(Polish Expression)或前缀表达式(Prefix Expression),如+AB;
  • 把运算符写在操作数之后,称为逆波兰表达式(Reverse Polish Expression)或后缀表达式(Suffix Expression),如AB+;

假设有一个中缀表达式a+bc-(d+e):
1.首先将这个中缀表达式的所有运算加括号((a+(bc))-(d+e))
2.然后将所有运算符放到括号后面,这样就变成了((a(bc)
)+ (de)+ )-
5. 把所有括号去掉abc*+de+ -,最后得出的结果就是后缀表达式。*

【题目描述】:
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

【解题】:
1、循环扫描语法单元的项目。
2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
4、将运算结果重新压入堆栈。
5、重复步骤2-4,堆栈中即为结果值。

public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(int i=0;i<tokens.length;i++){
            String str = tokens[i];
            if(!isOperation(str)) {
                int num = Integer.parseInt(str);
                stack.push(num);
            }else {
                int num1 = stack.pop();
                int num2 = stack.pop();
                switch(str){
                    case "+":
                        stack.push(num2+num1);
                        break;
                    case "-":
                        stack.push(num2-num1);
                        break;
                    case "*":
                        stack.push(num2*num1);
                        break;
                    case "/":
                        stack.push(num2/num1);
                        break;
                }
            }
        }
        return stack.pop();
    }

    public boolean isOperation(String str) {
        if (str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")) {
            return true;
        }
        return false;
    }

4.4. 20.括号匹配

【题目描述】:
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号
    在这里插入图片描述
    【解题】:
    只有三种情况下,括号不匹配,只要解决这三种情况,那么剩下的都是括号匹配的情况。
    在这里插入图片描述

解题步骤:
1.如果碰到左括号那么就把它放入栈中;
2.到遇到右括号时,应该和最后一个左括号(在栈中出栈的第一个数据)进行匹配,如果匹配就出栈。
3.最后判断栈中和输入的字符串中的元素是否为空
在这里插入图片描述

    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.empty()) {
                    // 此时,栈为空,但是字符串不为空,右括号多,不符合括号匹配的情况
                    return false;
                }
                // 获取最后一个左括号
                char tmp = stack.peek();
                // 进行括号匹配
                if (tmp == '(' && ch == ')' || tmp == '[' && ch == ']' || tmp == '{' && ch == '}') {
                    stack.pop();
                } else {
                    return false;
                }
            }
        }
        // 此时,如果栈不为空说明左括号多,栈为空说明左括号和右括号匹配
        return stack.empty();
    }

4.5 JZ31 栈的压入、弹出序列

JZ31 栈的压入、弹出序列
【题目描述】:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
【解题】:
1.遍历push数组,把元素放到栈中
2.每push一个元素,就和pop数组中的元素比较
3.如果相等j++并且出栈
4.如果不相等,就继续入栈
在这里插入图片描述

public boolean IsPopOrder (int[] pushV, int[] popV) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0; i < pushV.length; i++) {
            stack.push(pushV[i]);
            //如果栈中的栈顶的元素和弹出序列数组的元素相同,那么元素出栈并且j++
            while (!stack.empty() && j < popV.length && stack.peek() == popV[j]) {
                stack.pop();
                j++;
            }
        }
        //此时栈的压入顺序数组执行完了,如果循环执行结束,栈中还有元素说明弹出序列和压入序列不对应
        return stack.empty();
    }

4.6 155.最小的栈

155.最小的栈
【题目描述】:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

【解题】:
方法:辅助栈
在这里插入图片描述
按照如图的思路,我们需要设计一个数据结构,使得每个元素与其相应的最小值保持对应。因此我们可以使用一个辅助栈,用于存储元素对应的最小值。

  1. 当一个元素要入栈时,普通的栈一定要放元素,对于辅助栈如果是空的时,我们要放元素,不为空时我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值或者相等值,如果该元素较小或者相等,就将该元素插入辅助栈中;
  2. 当一个元素要出栈时,如果与当前辅助栈的栈顶存储的最小值相等,那我们把辅助栈的栈顶元素也一并弹出;不相等时只需要弹出普通栈的值。
  3. 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。
class MinStack {

Stack<Integer> stack;
    Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int val) {
        //普通栈一定要放元素元素
        stack.push(val);
        //如果辅助栈为空时,也要放元素
        if (minStack.empty()) {
            minStack.push(val);
        } else {
            //不为空时,如果该元素小于或者等于辅助栈的栈顶存储的最小值,就将该元素插入辅助栈中
            int peek = minStack.peek();
            if (val <= peek) {
                minStack.push(val);
            }
        }
    }

    public void pop() {
        //首先弹出普通栈的值
        int pop = stack.pop();
        //如果普通栈的值与当前辅助栈的栈顶存储的最小值相等,那我们把辅助栈的栈顶元素也一并弹出
        if (!minStack.empty()) {
            if (pop == minStack.peek()) {
                minStack.pop();
            }
        }
    }

    public int top() {
        if (!stack.empty()) {
            return stack.peek();
        }
        return -1;
    }

    public int getMin() {
        if (!minStack.empty()) {
            return minStack.peek();
        }
        return -1;
    }
}

5. 概念区分

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

  • 栈:栈是一种数据结构,它是一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。栈可以用于实现函数调用、表达式求值、内存管理等功能。

  • 虚拟机栈:虚拟机栈是指在计算机中运行的程序中,每个线程都有自己的虚拟机栈,用于存储线程中方法的局部变量、操作数栈、动态链接、返回地址等信息。虚拟机栈的大小可以在程序运行时动态调整。

  • 栈帧:栈帧是指在程序执行过程中,每个方法在虚拟机栈中所占用的一块内存空间,用于存储方法的局部变量、操作数栈、动态链接、返回地址等信息。当一个方法被调用时,会在虚拟机栈中创建一个新的栈帧,当方法执行结束时,栈帧会被销毁。

因此,栈是一种数据结构,虚拟机栈是指程序运行时的内存空间,而栈帧是指虚拟机栈中存储方法信息的一块内存空间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值