跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java 栈与队列原理及手写实现

Java 数据结构实战,涵盖栈与队列的手写实现。通过数组模拟栈操作,利用双链表与循环数组构建队列,并演示两者互转的经典算法场景。重点解析扩容机制、指针移动逻辑及边界条件处理,适合深入理解底层原理。

CloudNative发布于 2026/3/24更新于 2026/9/950 浏览
Java 栈与队列原理及手写实现

Java 栈与队列原理及手写实现

栈的核心概念

栈(Stack)是一种线性数据结构,核心特性是后进先出(LIFO)。想象一下子弹夹:最后装进去的子弹最先发射。栈底是最早放入的元素,栈顶则是最新进入的数据。

在实际应用中,栈常用于内存分配、表达式求值以及方法调用链的管理。理解栈的关键在于把握'栈顶'这一操作点的所有进出逻辑。

栈结构示意

数组模拟栈的实现

用数组实现栈非常直观,我们只需要一个指针 top 来标记当前栈顶位置。初始时 top 设为 -1,表示空栈。

主要操作包括:

  • push:入栈,元素放入 top+1 位置。
  • pop:出栈,返回 top 位置元素并将 top 减一。
  • peek:查看栈顶但不移除。
  • size / empty / full:辅助状态检查。

这里有个细节需要注意,当数组满了需要扩容。通常采用倍增策略,即容量翻倍,这样均摊时间复杂度更优。

import java.util.Arrays;

public class MyStack {
    private int[] elem;
    private int top; // 栈顶索引,也代表当前元素数量
    private static final int DEFAULT_CAPACITY = 10;

    public MyStack() {
        elem = new int[DEFAULT_CAPACITY];
        top = -1;
    }

    public void push(int item) {
        if (full()) {
            // 容量不足时扩容,通常是原来的两倍
            elem = Arrays.copyOf(elem, 2 * elem.length);
        }
        elem[++top] = item;
    }

    public int pop() throws RuntimeException {
        if (empty()) {
            throw new RuntimeException("栈为空");
        }
        return elem[top--];
    }

    public int peek() {
        if (empty()) {
            throw new RuntimeException("栈为空");
        }
        return elem[top];
    }

    public int size() {
        return top + 1;
    }

    public boolean empty() {
        return top == -1;
    }

    public boolean full() {
        return top == elem.length - 1;
    }
}

队列的基本概念

队列(Queue)遵循先进先出(FIFO)原则。数据从队尾(rear)进入,从队头(front)离开。就像排队买票一样,先来的人先处理。

队列操作主要包括入队(offer/enqueue)、出队(poll/dequeue)、查看队首(peek/front)等。

队列结构示意

双链表模拟队列

使用双链表实现队列可以避免数组扩容的问题,但空间开销稍大。我们需要维护 head 和 last 两个指针。

在插入新节点时,要注意双向链接的更新:新节点的 prev 指向原尾节点,原尾节点的 next 指向新节点,然后更新 last 指针。

public class MyQueue {
    static class Node {
        int elem;
        Node next;
        Node prev;

        Node(int elem) {
            this.elem = elem;
        }
    }

    private Node head;
    private Node last;
    private int size = 0;

    public void offer(int val) {
        Node queue = new Node(val);
        if (this.head == null) {
            this.head = queue;
            this.last = queue;
            ++size;
            return;
        }
        // 关键:更新尾部链接
        this.last.next = queue;
        queue.prev = this.last;
        this.last = queue;
        ++size;
    }

    public int poll() {
        if (this.head == null) {
            throw new RuntimeException("队列为空");
        }
        Node cur = this.head;
        if (this.head.next == null) {
            this.head = null;
            this.last = null;
        } else {
            this.head = this.head.next;
            this.head.prev = null;
        }
        size--;
        return cur.elem;
    }

    public int peek() {
        if (this.head != null) {
            return this.head.elem;
        }
        return 0;
    }

    public int size() {
        return size;
    }
}

循环队列(数组实现)

普通数组实现队列在频繁出队后会产生大量空隙,造成空间浪费。循环队列通过取模运算 (index + 1) % length 让下标回到数组头部,从而复用空间。

这里有一个经典陷阱:如何区分队满和队空?通常约定牺牲一个存储单元,或者增加一个 count 变量。下面的实现采用了牺牲一个单元的方式,即 rear 永远指向下一个可插入位置。

public class MyCircularQueue {
    private int[] array;
    private int front;
    private int rear;

    public MyCircularQueue(int k) {
        // 多开一个空间用于区分队空和队满
        array = new int[k + 1];
        front = 0;
        rear = 0;
    }

    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        array[rear] = value;
        rear = (rear + 1) % array.length;
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % array.length;
        return true;
    }

    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return array[front];
    }

    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        // rear 指向下一个空位,所以实际最后一个元素是 (rear - 1 + len) % len
        int temp = (rear == 0) ? array.length - 1 : rear - 1;
        return array[temp];
    }

    public boolean isEmpty() {
        return front == rear;
    }

    public boolean isFull() {
        return (rear + 1) % array.length == front;
    }
}

用队列实现栈

既然队列是 FIFO,而栈是 LIFO,单靠一个队列无法直接实现栈的弹出顺序。我们需要借助两个队列协作。

思路很简单:每次压入新元素时,将其放入辅助队列,然后将主队列的所有元素依次移到辅助队列中,最后交换两个队列的引用。这样,新元素就始终位于'队头',弹出时自然符合后进先出。

import java.util.LinkedList;
import java.util.Queue;

public class StackByQueue {
    Queue<Integer> q1;
    Queue<Integer> q2;

    public StackByQueue() {
        q1 = new LinkedList<>();
        q2 = new LinkedList<>();
    }

    public void push(int x) {
        // 将新元素放入非空队列或辅助队列
        q2.offer(x);
        while (!q1.isEmpty()) {
            q2.offer(q1.poll());
        }
        // 交换引用,确保 q1 始终是当前有效的栈
        Queue<Integer> tmp = q1;
        q1 = q2;
        q2 = tmp;
    }

    public int pop() {
        return q1.poll();
    }

    public int top() {
        return q1.peek();
    }

    public boolean empty() {
        return q1.isEmpty();
    }
}

用栈来实现队列

反过来,用两个栈模拟队列也是经典面试题。利用栈的倒序特性,我们可以把数据从栈 A 倒入栈 B,从而实现顺序反转。

具体策略:

  1. 入队时直接压入栈 A。
  2. 出队或查看队首时,如果栈 B 为空,则将栈 A 的所有元素弹出并压入栈 B。此时栈 B 的栈顶即为最早进入的元素。
  3. 如果栈 B 不为空,直接从栈 B 弹出,无需再次搬运。

这种惰性转移的策略保证了每个元素最多被移动两次,效率较高。

import java.util.Stack;

public class QueueByStack {
    Stack<Integer> A;
    Stack<Integer> B;

    public QueueByStack() {
        A = new Stack<>();
        B = new Stack<>();
    }

    public void push(int x) {
        A.push(x);
    }

    public int pop() {
        int check = peek();
        B.pop();
        return check;
    }

    public int peek() {
        if (!B.isEmpty()) {
            return B.peek();
        }
        if (A.isEmpty()) {
            return -1;
        }
        while (!A.isEmpty()) {
            B.push(A.pop());
        }
        return B.peek();
    }

    public boolean empty() {
        return A.isEmpty() && B.isEmpty();
    }
}

总结

栈和队列是基础数据结构中的基石。栈强调'后进先出',适合回溯、递归场景;队列强调'先进先出',适合任务调度、广度优先搜索。

手写实现时,重点在于边界条件的判断(如空栈/空队列)以及指针的移动逻辑。无论是数组扩容还是循环取模,都需要仔细处理索引计算,避免越界或死循环。掌握这些底层逻辑,对后续学习复杂算法至关重要。

目录

  1. Java 栈与队列原理及手写实现
  2. 栈的核心概念
  3. 数组模拟栈的实现
  4. 队列的基本概念
  5. 双链表模拟队列
  6. 循环队列(数组实现)
  7. 用队列实现栈
  8. 用栈来实现队列
  9. 总结

更多推荐文章

查看全部
  • 微信小程序城市公交查询与失物招领系统的开发笔记
  • C# ImageSharp 与 JavaScript Canvas 图像处理性能对比
  • LightRAG 详解:基于图结构的检索增强生成系统实践
  • 新手如何从零开始学习漏洞挖掘与渗透测试
  • OpenClaw 配置多 Agent 及多 QQ、飞书机器人
  • YOLO26:实时目标检测的关键架构改进与性能基准测试
  • ToDesk ToClaw 评测:基于 OpenClaw 的零门槛 AI 桌面自动化
  • Neo4j Desktop 2.0 安装教程及路径修改
  • 天然气管道内检测机器人检测节设计与结构分析
  • 飞书 OpenClaw 接入指南:无需服务器通过长连接运行机器人
  • VR 眼镜与自动验光仪的光学成像原理及视网膜适配技术
  • Claude/GPT/Codex 中转站选择指南:价格、稳定性与避坑要点
  • 解决 npm 安装 OpenClaw 时遇到的 Git 报错问题
  • MySQL 8.0 Windows 安装配置实战指南
  • 双指针算法实战:移动零与复写零详解
  • 基于 Higress 将 REST API 转换为 MCP Server 工具配置指南
  • 基于 SpringBoot 的青年公寓服务平台
  • VSCode 禁用 GitHub Copilot 功能
  • World Monitor:基于 AI 的全球情报态势感知仪表盘
  • Python 十大常用数据可视化工具详解与对比

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online