初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)

接上次博客:数据结构初阶(4)(OJ练习【判断链表中是否有环、返回链表入口点、删除链表中的所有重复出现的元素】、双向链表LinkedList【注意事项、构造方法、常用方法、模拟实现、遍历方法、顺序表和链表的区别)_di-Dora的博客-CSDN博客

目录

栈(Stack)的概念

栈的模拟实现

 栈的应用及练习

1. 改变元素的序列 :

2. 将递归转化为循环

3、括号匹配(出现概率高)

4、逆波兰表达式求值

5、出栈入栈次序匹配 

6、最小栈

链栈和顺序栈

栈、虚拟机栈、栈帧的区别


栈(Stack)的概念

栈(Stack)是一种常见的数据结构,它遵循后进先出(Last-In-First-Out,LIFO)的原则。它是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈可以想象成一堆盘子,你只能从最上面放入盘子或者取出最上面的盘子,无法直接访问或操作其他位置的盘子。

栈的两个基本操作是压栈(Push)和出栈(Pop):

1、压栈(Push):将一个元素添加到栈的顶部。新的元素成为栈的新顶部,原来的顶部元素在它下方。也可以说是将元素放入栈顶。

2、出栈(Pop):从栈的顶部移除一个元素,并返回该元素的值。栈的新顶部将是原来顶部下方的元素。也可以说是从栈顶取出元素。

初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第1张图片

 除了压栈和出栈操作外,栈还具有以下特点:

  • 栈是一种有限容量的数据结构,它的容量可以事先定义或根据需要动态增长。
  • 栈可以为空,即没有任何元素。
  • 当栈满时,继续进行压栈操作会导致栈溢出。
  • 当栈为空时,继续进行出栈操作会导致栈下溢。
  • 栈在计算机科学和编程中有广泛的应用,例如函数调用、表达式求值、括号匹配、逆序输出等。压栈和出栈是栈操作的基础,通过这两个操作可以实现对栈的基本操作和数据处理。

栈是很简单的,它的源代码就那么一点点,一共就四个方法: 初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第2张图片

package java.util;


public
class Stack extends Vector {

    public Stack() {
    }


    public E push(E item) {
        addElement(item);

        return item;
    }


    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }


    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }


    public boolean empty() {
        return size() == 0;
    }


    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }


    private static final long serialVersionUID = 1224463164541339165L;
}

我们可以简单使用一下: 

    public static void main(String[] args) {
        Stack stack = new Stack<>();
        stack.push(12);
        stack.push(23);
        stack.push(34);
        
        //删除
        Integer x = stack.pop();
        System.out.println(x);

        //获取栈顶元素但是不删除
        int ret = stack.peek();
        System.out.println(ret);

        ret = stack.peek(); //还是原来的那个元素
        System.out.println(ret);


        System.out.println(stack.size());

    }

栈的模拟实现

我们发现,栈的源代码里面好像不包含成员变量?

但是它继承了Vector,我们可以跳转过去看看:初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第3张图片

Vector是一种动态数组结构,在Java中被实现为一个类。它与普通的数组相似,但具有动态增长的能力。Vector可以根据需要自动调整其大小,以容纳任意数量的元素。

Vector内部使用一个对象数组来存储元素,并通过索引访问这些元素。与普通数组相比,

Vector具有以下特点:

  1. 动态大小:Vector的大小可以根据需要进行动态调整。当元素数量超过当前容量时,Vector会自动增加其容量,以容纳更多元素。这使得Vector非常适合在需要经常进行插入和删除操作的情况下使用。
  2. 线程安全:Vector是线程安全的,这意味着它可以在多线程环境中使用而无需额外的同步措施。Vector的方法在执行时会进行同步,以确保线程安全。

Vector类提供了一系列方法来操作和访问其中的元素,包括添加元素、删除元素、访问元素、搜索元素等。一些常用的方法包括:

  • add(element): 在Vector的末尾添加一个元素。
  • remove(element): 删除Vector中的指定元素。
  • get(index): 获取指定索引位置的元素。
  • size(): 返回Vector中元素的数量。
  • isEmpty(): 检查Vector是否为空。

需要注意的是,从Java 1.2版本开始,推荐使用更为高级的ArrayList类替代Vector,因为ArrayList在大多数情况下具有更好的性能。但如果需要在多线程环境中使用,或者需要与旧版本的Java代码兼容,仍然可以使用Vector。

 现在我们开始实现栈:

import java.util.Arrays;

public class MyStack {
    private int[] elem;
    private int usedSize;//不初始化,默认为0   可以代表下标

    private static final int DEFAULT_CAPACITY = 10;

    public MyStack() {
        this.elem = new int[DEFAULT_CAPACITY];
    }

    public void push(int val) {
        if(isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        this.usedSize++;
    }
    public boolean isFull() {
        return this.usedSize == this.elem.length;
    }

    public int pop() {
        if(isEmpty()) {
            throw new EmptyException();
        }
        int oldVal = elem[usedSize-1];
        this.usedSize--;
        return oldVal;
    }

    public int peek() {
        if(isEmpty()) {
            throw new EmptyException();
        }
        return elem[usedSize-1];
    }

    public boolean isEmpty() {
        return this.usedSize == 0;
    }
}

使用一下看看:

    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        myStack.push(1);
        myStack.push(2);
        myStack.push(3);

        System.out.println(myStack.peek());//3
        System.out.println(myStack.pop());//3
        System.out.println(myStack.pop());//2
        System.out.println(myStack.pop());//1
        System.out.println(myStack.isEmpty());//true
        System.out.println(myStack.pop());//异常
    }

初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第4张图片

 栈的应用及练习

大家应该发现了,栈的实现不难,但是题很多。

废话少说,现在开始:

1. 改变元素的序列 :

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

A: 1,4,3,2     例如:进来1,出1,进来2、3、4,再依次出栈,✔

B: 2,3,4,1

C: 3,1,4,2    要出来1,前面必要有2。

D: 3,4,2,1

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

A: 12345ABCDE

B: EDCBA54321

C: ABCDE12345

D: 54321EDCBA

2. 将递归转化为循环

比如:逆序打印链表

    // 递归方式
    void printList(Node head){
        if(null != head){
            printList(head.next);
            System.out.print(head.val + " ");
        }
    }

所以现在可以给我们之前的链表加上一个包含了栈的新的方法:逆序打印:

    // 循环方式
    public void reversePrintList(Node head){
        if(null == head){
            return;
        }
        Stack stack = new Stack<>();//给一个栈放节点
        Node cur = head;
        while (cur != null) {
            stack.push(cur);
            cur = cur.next;
        }
 
        while (!stack.isEmpty()) {
            Node top = stack.pop();
            System.out.print(top.val+" ");
        }
        System.out.println();

    }

3、括号匹配(出现概率高)

力扣:20. 有效的括号 - 力扣(Leetcode)

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

 初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第5张图片

 这道题为什么用栈比较好?

栈的插入和删除的时间复杂度是O(1)。

大致思路如下:

只要是左括号就入栈;遇到右括号就开始匹配;

当且仅当字符串遍历完,并且栈为空的时候才匹配。

初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第6张图片

public boolean isValid(String s) {
        Stack 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 top = stack.peek();
                //此时top是左括号 ch是右括号
                if(ch == ')' && top == '('  || ch == '}' && top == '{' || ch == ']' && top == '[') {
                    stack.pop();
                }else{
                    return false;//不匹配
                }
            }

        }
        if(!stack.empty()) {
            return false;//左括号不匹配
        }
        return true;
    }

或者: 

public class ValidParentheses {
    public boolean isValid(String s) {
        Stack stack = new Stack<>();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '{' || c == '[') {
                stack.push(c);
            } else if (c == ')' || c == '}' || c == ']') {
                if (stack.isEmpty()) {
                    return false; // 当前右括号没有匹配的左括号
                }
                char top = stack.pop();
                if ((c == ')' && top != '(') || (c == '}' && top != '{') || (c == ']' && top != '[')) {
                    return false; // 当前右括号与栈顶的左括号类型不匹配
                }
            }
        }
        return stack.isEmpty(); // 所有左括号都匹配完了,栈应该为空
    }

我们可以看一下力扣官方的题解: 

    class Solution {
        public boolean isValid(String s) {
            int n = s.length();
            if (n % 2 == 1) {
                return false;
            }
            // 使用一个映射来存储左右括号的对应关系
            Map pairs = new HashMap() {{
                put(')', '(');
                put(']', '[');
                put('}', '{');
            }};
            Deque stack = new LinkedList();
            for (int i = 0; i < n; i++) {
                char ch = s.charAt(i);
                if (pairs.containsKey(ch)) {
                    if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
                        return false;
                    }
                    stack.pop();
                } else {
                    stack.push(ch);
                }
            }
            return stack.isEmpty();
        }
    }

4、逆波兰表达式求值

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

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

力扣:150. 逆波兰表达式求值 - 力扣(Leetcode) 

要做这个题,我们需要先了解几个概念:

中缀表达式和后缀表达式(逆波兰表达式): 

1、中缀表达式是我们通常使用的表达式表示方法,其中运算符位于操作数的中间。

例如,2 + 3 * 4 就是一个中缀表达式。中缀表达式通常需要通过运算符优先级和括号—— ( 1 + 4 ) * 5 - 7 来确定运算的顺序。

2、后缀表达式,也称为逆波兰表达式(Reverse Polish Notation,RPN),是一种将运算符放置在操作数之后的表达式表示方法。在后缀表达式中,运算的顺序可以直接由表达式本身的结构决定,而无需括号或运算符优先级的考虑。例如,中缀表达式 2 + 3 * 4 可以转换为后缀表达式 2 3 4 * +。初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第7张图片

 为什么要有后缀表达式?

后缀表达式的计算可以通过使用栈来实现。遍历后缀表达式,遇到操作数时将其入栈,遇到运算符时从栈中取出相应数量的操作数进行计算(先弹出来的作为右操作数,后出来的作为左操作数),并将计算结果入栈。最后栈中剩下的元素就是最终的计算结果。

后缀表达式的优点是可以消除运算符优先级和括号带来的歧义,计算过程简单明了。它常用于计算机科学领域中的编译器、解释器和计算器等场景。

相应的,前缀表达式,也称为波兰前缀表达式,其中运算符位于操作数之前。例如,对于表达式 "2 + 3 * 4",在前缀表达式中可以表示为 "+ 2 * 3 4"。

    public int evalRPN(String[] tokens) {
        Stack stack = new Stack<>();
        for(String s : tokens) {
            if(!isOperation(s)) {
                //数字字符
                stack.push(Integer.parseInt(s));
            }else {
                // 有可能是加减乘除 当中的一个运算符
                int num2 = stack.pop();
                int num1 = stack.pop();
                switch(s) {
                    case "+":
                        stack.push(num1 + num2);
                        break;
                    case "-":
                        stack.push(num1 - num2);
                        break;
                    case "*":
                        stack.push(num1 * num2);
                        break;
                    case "/":
                        stack.push(num1 / num2);
                        break;
                }
            }
        }
        return stack.pop();
    }
    private boolean isOperation(String s) {
        if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
            return true;
        }
        return false;
    }

 或者:

import java.util.Stack;

class Solution {
    public int evalRPN(String[] tokens) {
        Stack stack = new Stack<>();

        for (String token : tokens) {
            if (isOperator(token)) {
                int num2 = stack.pop();
                int num1 = stack.pop();
                int result = calculate(num1, num2, token);
                stack.push(result);
            } else {
                stack.push(Integer.parseInt(token));
            }
        }

        return stack.pop();
    }

    private boolean isOperator(String token) {
        return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/");
    }

    private int calculate(int num1, int num2, String operator) {
        switch (operator) {
            case "+":
                return num1 + num2;
            case "-":
                return num1 - num2;
            case "*":
                return num1 * num2;
            case "/":
                return num1 / num2;
            default:
                return 0;
        }
    }
}

 这是力扣的官方题解,用数组实现:

class Solution {
    public int evalRPN(String[] tokens) {
        int n = tokens.length;
        int[] stack = new int[(n + 1) / 2];
        int index = -1;
        for (int i = 0; i < n; i++) {
            String token = tokens[i];
            switch (token) {
                case "+":
                    index--;
                    stack[index] += stack[index + 1];
                    break;
                case "-":
                    index--;
                    stack[index] -= stack[index + 1];
                    break;
                case "*":
                    index--;
                    stack[index] *= stack[index + 1];
                    break;
                case "/":
                    index--;
                    stack[index] /= stack[index + 1];
                    break;
                default:
                    index++;
                    stack[index] = Integer.parseInt(token);
            }
        }
        return stack[index];
    }
}

5、出栈入栈次序匹配 

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

  •  0<=pushV.length == popV.length <=1000
  •  -1000<=pushV[i]<=1000 
  • pushV 的所有数字均不相同

牛客网链接:栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com) 

这个题熟不熟悉?是不是就类似于我们刚刚做的选择题的代码实现? 

来挑战一下吧!

代码的大致实现思路如下:

  1. 创建一个栈对象 stack 用于存储压入序列的元素。
  2. 定义两个索引变量 pushIndex 和 popIndex,分别表示压入序列和弹出序列的当前位置。
  3. 通过一个循环遍历弹出序列,直到处理完所有的弹出元素。
  4. 在循环中,首先检查栈顶元素是否与当前弹出序列元素相同。如果相同,说明栈顶元素可以被直接弹出,栈顶元素和弹出序列索引均向后移动。
  5. 如果栈顶元素与当前弹出序列元素不同,说明需要将压入序列的元素继续压入栈中。如果还有未处理的压入元素,将其压入栈中,同时压入序列索引向后移动。
  6. 如果没有剩余的压入元素可用,说明无法满足当前的弹出序列,返回 false。
  7. 循环结束后,检查栈是否为空。如果所有的弹出序列元素都被处理完且栈为空,说明弹出序列是可能的,返回 true,否则返回 false。
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int[] pushA, int[] popA) {
        Stack stack = new Stack<>();
        int pushIndex = 0;
        int popIndex = 0;

        while (popIndex < popA.length) {
            // 如果栈顶元素和当前弹出序列元素相同,则直接弹出
            if (!stack.isEmpty() && stack.peek() == popA[popIndex]) {
                stack.pop();
                popIndex++;
            }
            // 如果栈为空或者栈顶元素和当前弹出序列元素不同
            else {
                // 如果还有元素可以压入栈,则将元素压入栈
                if (pushIndex < pushA.length) {
                    stack.push(pushA[pushIndex]);
                    pushIndex++;
                }
                // 否则说明无法满足当前的弹出序列,返回false
                else {
                    return false;
                }
            }
        }

        // 如果所有的弹出序列元素都被处理完,且栈为空,则说明弹出序列是可能的
        return stack.isEmpty();
    }
}

或者稍微变一下: 

    public class Solution {
        public boolean IsPopOrder(int[] pushA, int[] popA) {
            Stack stack = new Stack<>();
            int j=0;
            for(int i=0;i

这样好像更清晰明了,对吧?

6、最小栈

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

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

 力扣:155. 最小栈 - 力扣(Leetcode)初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第8张图片

import java.util.Stack;

class MinStack {
    private Stack stack;
    private Stack minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || val <= minStack.peek()) {
            minStack.push(val);
        }
    }
    
    public void pop() {
        if(!stack.empty()) {
            if (stack.peek().equals(minStack.peek())) {
                minStack.pop();
            }
        stack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

在MinStack类中,我们使用两个栈,一个用于存储数据的普通栈(stack),另一个用于存储最小元素的栈(minStack)。每当push一个新元素时,我们将其压入普通栈,并检查是否需要将其压入最小栈。如果最小栈为空或者新元素小于等于最小栈的栈顶元素,则将其压入最小栈。在pop操作时,如果普通栈的栈顶元素和最小栈的栈顶元素相等,则同时将它们出栈。getMin操作只需要返回最小栈的栈顶元素即可。

这样设计,无论是push、pop、top还是getMin操作,都可以在常数时间内完成。

我们可以再把上面的部分代码稍微展开,写得更明白一些:

class MinStack {

    private Stack stack;
    private Stack minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(minStack.empty()) {
            minStack.push(val);
        }else {
            if(val <= minStack.peek()) {
                minStack.push(val);
            }
        }
    }
    
    public void pop() {
        if(!stack.empty()) {
            int ret = stack.pop();
            if(minStack.peek() == ret) {
                minStack.pop();
            }
        }
    }
    //获取正常栈顶元素
    public int top() {
        if(stack.empty()) {
            return -1;
        }
        return stack.peek();
    }
    //获取最小栈顶元素
    public int getMin() {
        if(minStack.empty()) {
            return -1;
        }
        return minStack.peek();
    }
}

链栈和顺序栈

栈不止可以基于数组来实现,还可以基于链表实现:

链栈和顺序栈都是栈的实现方式,它们的主要区别在于数据的存储方式和操作的实现方式。

顺序栈(Sequential Stack):
顺序栈使用数组来实现,是一种连续存储的栈结构。栈顶指针指向数组的最后一个元素,当有新的元素入栈时,栈顶指针向后移动,指向新的栈顶元素。当元素出栈时,栈顶指针向前移动,指向新的栈顶元素。顺序栈的特点是访问元素的时间复杂度为O(1),但是在栈满时需要进行扩容操作。

链栈(Linked Stack):
链栈使用链表来实现,每个节点包含一个数据元素和一个指向下一个节点的指针。链栈的栈顶即链表的头节点,新元素入栈时,将其作为新的头节点。元素出栈时,将头节点移除,并将指针指向下一个节点。链栈的特点是不需要进行扩容操作,可以动态地分配内存,但是访问元素的时间复杂度为O(n),其中n是栈的长度。

初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第9张图片

初阶数据结构(5)(栈的概念、栈的模拟实现、栈的应用及练习【改变元素的序列 、 将递归转化为循环、括号匹配、逆波兰表达式求值、出栈入栈次序匹配、最小栈】、链栈和顺序栈栈、虚拟机栈、栈帧的区别)_第10张图片

 顺序栈适用于知道栈的最大容量或者容量变化不大的情况,可以实现高效的元素访问。链栈适用于容量变化较大或者不确定的情况,可以动态地分配内存,但是在访问元素时需要遍历链表,效率较低。选择使用哪种栈实现取决于具体的应用场景和需求。

栈、虚拟机栈、栈帧的区别

在计算机科学中,栈(Stack)、虚拟机栈(Virtual Machine Stack)和栈帧(Stack Frame)是相关概念,它们在不同的层次和上下文中有着不同的含义和功能。

栈(Stack):

栈是一种数据结构,用于存储和管理数据的一种方式。它遵循后进先出(LIFO)的原则,即最后进入栈的元素将首先被移除。
在计算机中,栈通常被用于管理函数调用和返回,以及保存临时数据等。
栈可以在内存中的任何位置实现,通常具有固定的大小。


虚拟机栈(Virtual Machine Stack):

虚拟机栈是在虚拟机(如Java虚拟机)中的一种数据结构,用于支持程序的执行。
每个线程在虚拟机中都有自己的虚拟机栈,用于存储方法调用的信息
虚拟机栈的大小可以在虚拟机启动时预先定义,或者根据需要动态调整。


栈帧(Stack Frame):

栈帧是在函数调用过程中,用于存储有关调用函数的信息和局部变量的数据结构
每个函数调用都会创建一个新的栈帧,它包含了函数的参数、局部变量、返回地址等。
栈帧通常由一些特定的字段组成,如局部变量表、操作数栈、动态链接等,用于支持函数的执行和返回。


简而言之,栈是一种通用的数据结构,虚拟机栈是在虚拟机中为每个线程维护的数据结构,而栈帧是在函数调用中用于存储函数执行信息的数据结构。栈帧是虚拟机栈中的一个元素,用于支持函数调用和执行。

你可能感兴趣的:(易错知识点,数据结构初阶,数据结构,算法,java,学习,栈)