当前位置: 首页 > news >正文

013【入门】队列和栈-链表、数组实现

🔍 队列和栈详解 | [数据结构]-[入门]-[基础组件]

▶ JDK8+ | ⏱️ 操作时间复杂度 O(1)

核心应用场景

  1. 队列应用场景

    • 任务调度系统:操作系统进程调度、线程池任务分配
    • 消息队列:分布式系统中的异步消息传递
    • 缓冲区管理:生产者-消费者模式的数据缓冲
    • 广度优先搜索:图算法和树的层序遍历
  2. 栈应用场景

    • 函数调用栈:程序执行时的方法调用管理
    • 表达式求值:中缀、后缀表达式的计算
    • 括号匹配:编译器语法检查、代码格式化
    • 深度优先搜索:图算法和树的遍历

核心概念对比

特性队列 (Queue)栈 (Stack)
数据结构特性先进先出 (FIFO)后进先出 (LIFO)
插入操作offer() - 队尾插入push() - 栈顶插入
删除操作poll() - 队首删除pop() - 栈顶删除
查看操作peek() - 查看队首peek() - 查看栈顶
典型应用BFS、任务调度DFS、函数调用

实现方式对比

实现方式时间复杂度空间复杂度优点缺点适用场景
链表实现O(1)O(n)动态扩容,无需预分配额外引用开销,内存碎片元素数量不确定,频繁增删
数组实现O(1)O(n)内存连续,访问高效固定容量,可能溢出元素数量可预估,性能敏感
环形数组O(1)O(n)空间复用,避免假溢出实现复杂,边界条件多固定大小缓冲区,消息队列

💻 队列实现

1. 链表实现队列 [Java]-[入门]-[集合框架]

/*** 基于链表的队列实现* 核心思想:使用单链表,头部出队,尾部入队* 时间复杂度:所有操作O(1)* 空间复杂度:O(n)*/
public class LinkedQueue<T> {/*** 链表节点定义*/private static class Node<T> {T data;           // 节点数据Node<T> next;     // 指向下一个节点的引用Node(T data) {this.data = data;this.next = null;}}private Node<T> head;  // 队首指针private Node<T> tail;  // 队尾指针private int size;      // 队列大小/*** 构造函数,初始化空队列*/public LinkedQueue() {head = tail = null;size = 0;}/*** 元素入队(队尾插入)* @param data 待入队元素*/public void offer(T data) {Node<T> newNode = new Node<>(data);if (isEmpty()) {// 空队列时,头尾指针都指向新节点head = tail = newNode;} else {// 非空队列时,在尾部添加新节点tail.next = newNode;tail = newNode;}size++;}/*** 元素出队(队首删除)* @return 队首元素,队列为空时返回null*/public T poll() {if (isEmpty()) {return null;}T data = head.data;head = head.next;// 如果队列变空,尾指针也要置空if (head == null) {tail = null;}size--;return data;}/*** 查看队首元素(不删除)* @return 队首元素,队列为空时返回null*/public T peek() {return isEmpty() ? null : head.data;}/*** 判断队列是否为空* @return 队列是否为空*/public boolean isEmpty() {return head == null;}/*** 获取队列大小* @return 队列中元素个数*/public int size() {return size;}
}

性能特点

  • ⏱️ 时间复杂度:offer、poll、peek均为O(1)
  • 🧠 空间复杂度:O(n),n为队列中元素个数
  • 🔍 内存布局:节点分散存储,每个节点额外8字节引用开销
  • ⚠️ 注意事项:需要维护头尾两个指针,空队列时的边界处理

2. 数组实现队列 [Java]-[入门]-[数组操作]

/*** 基于数组的队列实现(简单版本)* 核心思想:使用头尾指针标记队列边界* 时间复杂度:所有操作O(1)* 空间复杂度:O(n)* ⚠️ 缺陷:不支持空间复用,存在假溢出问题*/
public class ArrayQueue {private int[] queue;     // 存储队列元素的数组private int head;        // 队首指针private int tail;        // 队尾指针(指向下一个空位)private final int capacity;  // 队列容量/*** 构造函数,初始化指定容量的队列* @param capacity 队列容量*/public ArrayQueue(int capacity) {this.capacity = capacity;this.queue = new int[capacity];this.head = 0;this.tail = 0;}/*** 元素入队(队尾插入)* @param num 待入队元素* @return 入队是否成功*/public boolean offer(int num) {// 检查队列是否已满(假溢出检查)if (tail >= capacity) {return false;}queue[tail++] = num;return true;}/*** 元素出队(队首删除)* @return 队首元素* @throws RuntimeException 队列为空时抛出异常*/public int poll() {if (isEmpty()) {throw new RuntimeException("队列为空,无法出队");}return queue[head++];}/*** 查看队首元素(不删除)* @return 队首元素* @throws RuntimeException 队列为空时抛出异常*/public int peek() {if (isEmpty()) {throw new RuntimeException("队列为空,无法查看");}return queue[head];}/*** 判断队列是否为空* @return 队列是否为空*/public boolean isEmpty() {return head >= tail;}/*** 获取队列当前大小* @return 队列中元素个数*/public int size() {return tail - head;}/*** 判断队列是否已满* @return 队列是否已满*/public boolean isFull() {return tail >= capacity;}
}

性能特点

  • ⏱️ 时间复杂度:offer、poll、peek均为O(1)
  • 🧠 空间复杂度:O(n),n为预分配容量
  • 🔍 实现细节:tail指针指向下一个空位,head指针指向队首元素
  • ⚠️ 假溢出问题:当head > 0时,前面的空间无法复用,导致空间浪费

算法要点说明

  1. 指针含义:head指向队首元素,tail指向下一个可插入位置
  2. 边界条件:空队列时head == tail,满队列时tail == capacity
  3. 空间浪费:出队操作只移动head指针,不回收前面空间
  4. 改进方向:使用环形数组解决假溢出问题

🔄 栈实现

1. 数组实现栈 [Java]-[入门]-[数组操作]

/*** 基于数组的栈实现* 核心思想:数组尾部作为栈顶,使用size指针管理栈顶位置* 时间复杂度:所有操作O(1)* 空间复杂度:O(n)*/
public class ArrayStack {private int[] stack;        // 存储栈元素的数组private int size;           // 栈顶指针(指向下一个空位)private final int capacity; // 栈容量/*** 构造函数,初始化指定容量的栈* @param capacity 栈容量*/public ArrayStack(int capacity) {this.capacity = capacity;this.stack = new int[capacity];this.size = 0;}/*** 元素入栈* @param num 待入栈元素* @return 入栈是否成功*/public boolean push(int num) {// 检查栈是否已满if (size >= capacity) {return false;}stack[size++] = num;return true;}/*** 元素出栈* @return 栈顶元素* @throws RuntimeException 栈为空时抛出异常*/public int pop() {if (isEmpty()) {throw new RuntimeException("栈为空,无法出栈");}return stack[--size];}/*** 查看栈顶元素(不删除)* @return 栈顶元素* @throws RuntimeException 栈为空时抛出异常*/public int peek() {if (isEmpty()) {throw new RuntimeException("栈为空,无法查看");}return stack[size - 1];}/*** 判断栈是否为空* @return 栈是否为空*/public boolean isEmpty() {return size == 0;}/*** 判断栈是否已满* @return 栈是否已满*/public boolean isFull() {return size >= capacity;}/*** 获取栈当前大小* @return 栈中元素个数*/public int size() {return size;}
}

2. 链表实现栈 [Java]-[入门]-[集合框架]

/*** 基于链表的栈实现* 核心思想:链表头部作为栈顶,头插法实现入栈,头删法实现出栈* 时间复杂度:所有操作O(1)* 空间复杂度:O(n)*/
public class LinkedStack<T> {/*** 链表节点定义*/private static class Node<T> {T data;           // 节点数据Node<T> next;     // 指向下一个节点的引用Node(T data) {this.data = data;this.next = null;}}private Node<T> top;  // 栈顶指针private int size;     // 栈大小/*** 构造函数,初始化空栈*/public LinkedStack() {top = null;size = 0;}/*** 元素入栈(头插法)* @param data 待入栈元素*/public void push(T data) {Node<T> newNode = new Node<>(data);newNode.next = top;top = newNode;size++;}/*** 元素出栈(头删法)* @return 栈顶元素*/public T pop() {if (isEmpty()) {return null;}T data = top.data;top = top.next;size--;return data;}/*** 查看栈顶元素(不删除)* @return 栈顶元素*/public T peek() {return isEmpty() ? null : top.data;}/*** 判断栈是否为空* @return 栈是否为空*/public boolean isEmpty() {return top == null;}/*** 获取栈大小* @return 栈中元素个数*/public int size() {return size;}
}

性能特点对比

实现方式时间复杂度空间复杂度优点缺点
数组实现O(1)O(n)内存连续,缓存友好固定容量,可能溢出
链表实现O(1)O(n)动态扩容,无容量限制额外引用开销,内存碎片

算法要点说明

  1. 数组栈:size指针指向下一个空位,栈顶元素在stack[size-1]
  2. 链表栈:top指针指向栈顶元素,使用头插法和头删法
  3. 边界处理:空栈检查是关键,避免数组越界和空指针异常
  4. 性能优势:比Java内置Stack类(基于Vector)效率更高

🌟 环形队列实现 [算法]-[中级]-[高频考点]

/*** 环形队列实现(力扣622题标准解法)* 核心思想:使用环形数组解决假溢出问题,通过size变量区分空满状态* 时间复杂度:所有操作O(1)* 空间复杂度:O(n)* 🔗 LeetCode 622: https://leetcode.cn/problems/design-circular-queue/*/
class MyCircularQueue {private int[] queue;     // 环形数组private int head;        // 队首指针private int tail;        // 队尾指针private int size;        // 当前元素数量private final int capacity;  // 队列容量/*** 构造函数,设置队列长度为k* @param k 队列容量*/public MyCircularQueue(int k) {this.capacity = k;this.queue = new int[k];this.head = 0;this.tail = 0;this.size = 0;}/*** 向循环队列插入一个元素* @param value 待插入元素* @return 是否成功插入*/public boolean enQueue(int value) {if (isFull()) {return false;}queue[tail] = value;tail = (tail + 1) % capacity;  // 环形移动size++;return true;}/*** 从循环队列中删除一个元素* @return 是否成功删除*/public boolean deQueue() {if (isEmpty()) {return false;}head = (head + 1) % capacity;  // 环形移动size--;return true;}/*** 获取队首元素* @return 队首元素,队列为空时返回-1*/public int Front() {if (isEmpty()) {return -1;}return queue[head];}/*** 获取队尾元素* @return 队尾元素,队列为空时返回-1*/public int Rear() {if (isEmpty()) {return -1;}// 获取队尾元素索引:tail的前一个位置int rearIndex = (tail - 1 + capacity) % capacity;return queue[rearIndex];}/*** 检查队列是否为空* @return 队列是否为空*/public boolean isEmpty() {return size == 0;}/*** 检查队列是否已满* @return 队列是否已满*/public boolean isFull() {return size == capacity;}
}

环形队列可视化

enQueue(x)
deQueue()
Front()
Rear()
环形数组初始化
head=0, tail=0, size=0
执行操作
队列是否已满
返回false
queue[tail] = x
tail = (tail+1) % capacity
size++
队列是否为空
返回false
head = (head+1) % capacity
size--
队列是否为空
返回-1
返回queue[head]
队列是否为空
返回-1
计算rearIndex
返回queue[rearIndex]

核心技巧

  1. 环形移动公式(index + 1) % capacity 实现指针环形递增
  2. 队尾索引计算(tail - 1 + capacity) % capacity 获取队尾元素位置
  3. 状态判断优化:使用size变量避免head == tail的歧义
  4. 数学证明
    • 空队列:size == 0
    • 满队列:size == capacity
    • 有效元素:从head到tail(环形)共size个

算法要点说明

  1. 防溢出处理(tail - 1 + capacity) % capacity 确保负数取模正确
  2. 空间复用:解决普通数组队列的假溢出问题
  3. 边界条件:head和tail可以在数组中任意位置,通过size判断状态
  4. 工程优化:相比两指针方案,减少了边界判断的复杂度

🔄 数据结构相互实现 [算法]-[中级]-[面试高频]

1. 用栈实现队列 [力扣232]

/*** 用栈实现队列的通用模板* 核心思想:双栈设计,一个负责入队,一个负责出队* 🔗 LeetCode 232: https://leetcode.cn/problems/implement-queue-using-stacks/*/
class MyQueue {private Stack<Integer> inStack;   // 入队栈private Stack<Integer> outStack;  // 出队栈/*** 构造函数,初始化两个栈*/public MyQueue() {inStack = new Stack<>();outStack = new Stack<>();}/*** 元素入队* @param x 待入队元素*/public void push(int x) {inStack.push(x);}/*** 元素出队* @return 队首元素*/public int pop() {// 确保outStack有元素if (outStack.isEmpty()) {// 将inStack所有元素转移到outStackwhile (!inStack.isEmpty()) {outStack.push(inStack.pop());}}return outStack.pop();}/*** 查看队首元素* @return 队首元素*/public int peek() {if (outStack.isEmpty()) {while (!inStack.isEmpty()) {outStack.push(inStack.pop());}}return outStack.peek();}/*** 判断队列是否为空* @return 队列是否为空*/public boolean empty() {return inStack.isEmpty() && outStack.isEmpty();}
}

2. 用队列实现栈 [力扣225]

/*** 用队列实现栈(单队列优化版本)* 核心思想:每次push后,将前面的元素重新排列到队尾* 🔗 LeetCode 225: https://leetcode.cn/problems/implement-stack-using-queues/*/
class MyStack {private Queue<Integer> queue;/*** 构造函数,初始化队列*/public MyStack() {queue = new LinkedList<>();}/*** 元素入栈* @param x 待入栈元素*/public void push(int x) {int size = queue.size();queue.offer(x);// 将前面的元素重新排列,使新元素位于队首for (int i = 0; i < size; i++) {queue.offer(queue.poll());}}/*** 元素出栈* @return 栈顶元素*/public int pop() {return queue.poll();}/*** 查看栈顶元素* @return 栈顶元素*/public int top() {return queue.peek();}/*** 判断栈是否为空* @return 栈是否为空*/public boolean empty() {return queue.isEmpty();}
}

复杂度分析

用栈实现队列
  • 摊还时间复杂度:虽然单次pop可能是O(n),但n次操作的总时间仍为O(n)
  • 分析原理:每个元素最多被移动2次(inStack→outStack→出队),总移动次数为O(n)
  • 实际性能:在实际使用中,大部分pop操作都是O(1)的
用队列实现栈
  • 时间复杂度:push为O(n),pop、top、empty为O(1)
  • 空间复杂度:O(n),n为栈中元素个数
  • 性能权衡:push操作较慢,但其他操作保持高效

性能对比表格

操作原生队列栈实现队列摊还分析原生栈队列实现栈
push/offerO(1)O(1)O(1)O(1)O(n)
pop/pollO(1)O(1)/O(n)O(1)O(1)O(1)
peek/topO(1)O(1)/O(n)O(1)O(1)O(1)
emptyO(1)O(1)O(1)O(1)O(1)

视频链接

左老师的视频链接
013【入门】队列和栈-链表、数组实现

http://www.dtcms.com/a/284474.html

相关文章:

  • IPC进程间通信 interprocess communicate
  • Expr系列1(函数,表达式,约束系统)
  • Claude Code如何集成到VSCode、PyCharm IDE及使用技巧
  • 云手机的具体技术要求有什么?
  • Flutter:上传图片,选择相机或相册:wechat_assets_picker
  • docker 容器无法使用dns解析域名异常问题排查
  • 微服务的编程测评系统3-加密-日志-apifox-nacos-全局异常
  • Kubernetes (k8s)环境重启Pod方式总结
  • CPU架构、三级缓存以及内存优化屏障
  • Allure + JUnit5
  • 打造风险评估体系,筑牢城市安全基石
  • 用Python实现神经网络(二)
  • 解决hadoop常用到的问题
  • TRAE IDE** 下载、安装、开发、测试和部署 2048 小游戏的全流程指南
  • Linux内核内存管理相关的配置参数
  • 腾讯会议本地录屏转存失败解决办法
  • React 18 vs Vue3:状态管理方案深度对比
  • 如何实现微信小程序引导组件【添加到我的小程序】+ 附源码
  • 2025最新版Python 3.13.5安装使用指南
  • AI+医疗!VR和MR解剖学和针灸平台,智能时代如何重塑健康未来
  • wx小程序原生开发使用高德地图api
  • Qt初阶开发:QMediaPlayer的介绍和使用
  • 工业控制线路板是什么?工控PCB的核心原理和设计要点
  • git基本操作【GIT-2】
  • Ubuntu20.04 安装qt5.12.8
  • 用Python实现神经网络(一)
  • 基于FPGA的IIC控制EEPROM读写(2)
  • 解决 MyBatis/MyBatis-Plus 中 UUID 类型转换错误的最佳实践
  • OC—初识UIStackView
  • 线程安全集合——CopyOnWriteArrayList