【数据结构】栈Stack与队列Queue深度解析

一、栈(Stack):后进先出的线性结构

1.1 核心概念与操作特性

栈是仅允许在固定一端进行插入和删除操作的特殊线性表,其核心要素与规则如下:

  • 关键术语:进行数据操作的一端称为栈顶(Top),另一端称为栈底(Bottom)
  • 核心原则:遵循后进先出(LIFO,Last In First Out) 规则,即最后进入栈的元素会最先被取出,类似日常生活中叠放的盘子——新盘子放在顶端,取盘子时也从顶端开始 。
  • 基础操作
    • 压栈(Push):又称进栈、入栈,指将元素插入栈顶的操作 。
    • 出栈(Pop):指将栈顶元素删除并取出的操作,出栈操作仅能在栈顶进行 。

1.2 Java中Stack类的使用方法

Java中的Stack类提供了栈的完整实现,其核心方法及功能如下表所示 :

方法功能描述返回值/说明
Stack()构造一个空的栈空栈对象
E push(E e)将元素e入栈,操作位置为栈顶返回被入栈的元素e
E pop()移除栈顶元素并返回该元素栈顶元素,若栈为空可能抛出异常
E peek()获取栈顶元素,但不删除该元素栈顶元素,若栈为空可能抛出异常
int size()统计栈中有效元素的个数非负整数,代表元素数量
boolean empty()检测栈是否为空栈空返回true,否则返回false

代码示例与执行逻辑

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

上述代码清晰展示了栈的pushpeekpop等操作的执行效果,每一步操作均围绕栈顶进行,严格遵循LIFO原则 。

1.3 栈的底层模拟实现

1.3.1 底层结构选择

从Java集合框架的继承关系可知,Stack类继承自Vector类,而Vector是线程安全的动态顺序表,因此栈的底层本质是基于数组实现的 。基于数组实现栈时,需维护两个核心要素:存储元素的数组array和记录有效元素个数的size(同时作为栈顶指针,size-1即为栈顶元素下标)。

1.3.2 完整实现代码

import java.util.Arrays;

public class MyStack {
    int[] array;  // 存储栈元素的数组
    int size;     // 栈中有效元素个数(栈顶指针)


    public MyStack() {
        array = new int[3];
    }

    // 压栈:将元素e入栈,返回e
    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]; // 返回栈顶元素(size-1为栈顶下标)
    }

    // 统计有效元素个数
    public int size() {
        return size;
    }

    // 检测栈是否为空
    public boolean empty() {
        return size == 0;
    }

    // 动态扩容:当元素个数达到数组容量时,将容量翻倍
    private void ensureCapacity() {
        if (size == array.length) {
            array = Arrays.copyOf(array, size * 2);
        }
    }
}

核心逻辑说明

  • 扩容机制:通过ensureCapacity方法实现动态扩容,当size等于数组长度时,使用Arrays.copyOf将数组容量翻倍,避免元素溢出 。
  • 空栈处理peek方法中添加空栈检测,若栈为空则抛出运行时异常,保证操作安全性 。

1.4 概念辨析:栈、虚拟机栈、栈帧

三者分属不同层面,极易混淆,文档特别强调了其区别 :

  • :本文讨论的数据结构,是一种逻辑结构,用于存储数据并遵循LIFO规则。
  • 虚拟机栈:Java虚拟机的内存区域,属于运行时数据区,用于存储方法调用的相关信息。
  • 栈帧:虚拟机栈中的基本存储单位,每个方法被调用时会创建一个栈帧,包含局部变量表、操作数栈、动态链接等信息,方法执行完毕后栈帧出栈。

二、队列(Queue):先进先出的线性结构

2.1 核心概念与操作特性

队列是仅允许在一端插入、另一端删除的特殊线性表,其核心要素与规则如下:

  • 关键术语:进行插入操作的一端称为队尾(Tail/Rear),进行删除操作的一端称为队头(Head/Front)
  • 核心原则:遵循先进先出(FIFO,First In First Out) 规则,即先进入队列的元素会最先被取出,类似日常生活中的排队——先排队者先接受服务 。
  • 基础操作
    • 入队列(Enqueue):将元素插入队尾的操作 。
    • 出队列(Dequeue):将队头元素删除并取出的操作 。

2.2 Java中Queue接口的使用方法

2.2.1 接口特性与实现类

在Java中,Queue是一个接口,无法直接实例化,其底层通常通过链表实现。由于LinkedList类实现了Queue接口,因此实例化时需使用LinkedList

2.2.2 核心方法与功能

Queue接口的核心方法及功能如下表所示 :

方法功能描述返回值/说明
boolean offer(E e)将元素e入队列,操作位置为队尾入队成功返回true,失败返回false
E poll()移除队头元素并返回该元素队头元素,若队为空返回null
E peek()获取队头元素,但不删除该元素队头元素,若队为空返回null
int size()统计队列中有效元素的个数非负整数,代表元素数量
boolean isEmpty()检测队列是否为空队空返回true,否则返回false

2.2.3 代码示例与执行逻辑

public static void main(String[] args) {
    Queue<Integer> q = new LinkedList<>();
    q.offer(1); // 队尾入队 [1]
    q.offer(2); // [1,2]
    q.offer(3); // [1,2,3]
    q.offer(4); // [1,2,3,4]
    q.offer(5); // [1,2,3,4,5]
    System.out.println(q.size()); // 5
    System.out.println(q.peek()); // 1
    q.poll(); // 1出队,[2,3,4,5]
    System.out.println(q.poll()); // 2(2出队,[3,4,5])
    if(q.isEmpty()){
        System.out.println("队列空");
    }else{
        System.out.println(q.size()); // 3
    }
}

上述代码中,入队操作均在队尾进行,出队操作均在队头进行,完全遵循FIFO原则 。

2.3 队列的底层模拟实现

2.3.1 底层结构选择

队列的底层实现可选择顺序结构或链式结构,但链式结构更优 。原因在于:

  • 顺序结构的队头删除操作会导致后续所有元素前移,时间复杂度为O(n);
  • 链式结构的队头删除、队尾插入操作均可在O(1)时间内完成(维护队头first和队尾last指针实现)。

2.3.2 完整实现代码(基于双向列表)

public class MyQueue {

    public static class ListNode {
        ListNode next;  
        ListNode prev; 
        int value;    

        ListNode(int value) {
            this.value = value;
        }
    }

    ListNode first;  
    ListNode last;   
    int size = 0;    

    // 入队列
    public void offer(int e) {
        ListNode newNode = new ListNode(e); 
        if (first == null) {  
            first = newNode;
        } else {  
            last.next = newNode;
            newNode.prev = last;
        }
        last = newNode; 
        size++;        
    }

    // 出队列
    public Integer poll() {
        if (first == null) {  
            return null;
        }
        int value = first.value;
        if (first == last) {     
            first = null;
            last = null;
        } else {                
            first = first.next;  
            first.prev.next = null;
            first.prev = null;  
        }
        size--; 
        return value;
    }

    // 获取队头元素
    public Integer peek() {
        if (first == null) {
            return null;
        }
        return first.value;  
    }

    // 有效元素个数
    public int size() {
        return size;
    }

    // 判空
    public boolean isEmpty() {
        return first == null; 
    }
}

2.5 双端队列(Deque):灵活的双向操作结构

2.5.1 概念与特性

双端队列(Deque,全称“double ended queue”)是一种允许在两端进行入队和出队操作的队列,兼具栈和队列的特性 。其操作灵活性体现在:

  • 可从队头入队/出队;
  • 可从队尾入队/出队。

2.5.2 Java中的Deque接口使用

  • 接口与实现类Deque是Java中的接口,使用时需实例化其实现类,常用LinkedList(链式实现)和ArrayDeque(线性实现) 。
  • 替代栈与队列:在实际工程中,Deque的使用频率远高于单独的StackQueue,因其可灵活实现两种结构的功能 :
  • 用Deque实现栈(默认操作栈顶,即双端队列的一端)
  Deque<Integer> stack = new ArrayDeque<>();
  • 用Deque实现队列(默认队尾入、队头出)
  Deque<Integer> queue = new LinkedList<>();

三、总结

栈和队列作为两种约束性线性结构,其核心差异在于操作规则:栈遵循LIFO,适合处理“后进先出”的场景(如递归转化、括号匹配);队列遵循FIFO,适合处理“先进先出”的场景(如任务调度、缓冲区管理) 。

从实现角度看,栈的底层可基于数组或链表,Java中Stack类继承自Vector(数组实现);队列的底层优先选择链表,Java中Queue接口通过LinkedList实现,而循环队列则基于数组优化空间利用 。双端队列Deque则凭借双向操作的灵活性,成为工程中替代栈和队列的优选 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值