数据结构:队列(Queue)
—— 后端开发中最常用、最容易被忽略的“先进先出”神器
队列是数据结构中最简单却又极其重要的线性结构之一,它的核心特性只有一句话:
先进先出(FIFO – First In First Out)
就像排队买奶茶:先来的先拿到,先排队的人先走。
1. 队列的核心操作(必须掌握的 5 个)
| 操作 | 英文名称 | 时间复杂度 | 说明 |
|---|---|---|---|
| 入队 | enqueue / offer | O(1) | 从队尾添加元素 |
| 出队 | dequeue / poll | O(1) | 从队首移除并返回元素 |
| 查看队首 | peek / front | O(1) | 只看队首元素,不移除 |
| 判断空 | isEmpty | O(1) | 判断队列是否为空 |
| 获取长度 | size | O(1) | 返回当前队列中的元素个数 |
注意:好的队列实现中,上面所有操作都应该是 O(1) 的。
2. 队列的几种常见实现方式对比(2025-2026面试最常问)
| 实现方式 | 底层结构 | 入队复杂度 | 出队复杂度 | 特点与适用场景 | 推荐指数 |
|---|---|---|---|---|---|
| 顺序队列(数组) | 固定大小数组 | O(1) | O(n) | 简单,但出队效率低(需前移元素) | ★★☆☆☆ |
| 循环队列(数组) | 固定/动态数组+头尾指针 | O(1) | O(1) | 最高效,空间利用率高(最推荐) | ★★★★★ |
| 链表队列 | 单向链表 | O(1) | O(1) | 空间动态扩展,无上限,内存不连续 | ★★★★☆ |
| 双端队列(Deque) | 双向链表/数组 | O(1) | O(1) | 头尾都可进出(Java LinkedList/ArrayDeque) | ★★★★☆ |
| 优先队列(PriorityQueue) | 堆(二叉堆) | O(log n) | O(log n) | 不是严格的FIFO,按优先级出队 | ★★★☆☆ |
结论:
日常开发中 90% 的队列需求,用循环队列(数组实现)或链表队列就够了。
性能要求极高或需要动态扩容 → 优先用循环队列(带动态扩容)。
3. 最经典的循环队列实现(Java版,面试常考)
public class CircularQueue {
private int[] data;
private int front; // 指向队首元素
private int rear; // 指向队尾下一个空位
private int size; // 当前元素个数
private int capacity;
public CircularQueue(int k) {
capacity = k;
data = new int[k];
front = 0;
rear = 0;
size = 0;
}
public boolean enQueue(int value) {
if (isFull()) {
return false;
}
data[rear] = value;
rear = (rear + 1) % capacity;
size++;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
front = (front + 1) % capacity;
size--;
return true;
}
public int Front() {
if (isEmpty()) return -1;
return data[front];
}
public int Rear() {
if (isEmpty()) return -1;
return data[(rear - 1 + capacity) % capacity];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
}
关键点记忆口诀:
“rear 永远指向下一个空位”
“满的判断:size == capacity”
“空的判断:size == 0”
“取尾元素:(rear-1 + capacity) % capacity”
4. 队列的经典应用场景(后端最常遇到)
| 场景 | 队列作用 | 典型技术选型 |
|---|---|---|
| 消息队列 | 异步解耦、削峰填谷 | RabbitMQ / Kafka / Redis List |
| 任务队列 | 定时任务、延时任务、异步处理 | DelayQueue / Redis ZSET |
| BFS(广度优先搜索) | 层序遍历、求最短路径 | LinkedList 作为队列 |
| 滑动窗口最大值 | 维护窗口内最大值 | 双端队列(Deque) |
| 生产者-消费者模型 | 线程间通信 | BlockingQueue(ArrayBlockingQueue) |
| 请求缓冲池 | 高并发场景限流、缓冲请求 | LinkedBlockingQueue |
| 打印机任务队列 | 先来先服务 | 顺序队列 |
5. 后端面试最常考的队列相关问题(建议全部手写)
- 用两个栈实现队列(经典)
- 用队列实现栈(相对少见)
- 滑动窗口最大值(双端队列)
- 最近请求次数(队列 + 时间戳)
- 设计循环队列(LeetCode 622)
- 设计双端队列(LeetCode 641)
6. 一句话总结(背下来就赢了)
“队列的核心就是先进先出,核心难点在于循环数组的边界处理和判空判满条件”
后端真正常用的队列:
- 普通队列 → LinkedList / ArrayDeque
- 阻塞队列 → LinkedBlockingQueue / ArrayBlockingQueue
- 优先队列 → PriorityQueue
- 延迟队列 → DelayQueue / Redis ZSET
你现在对队列最想搞清楚的是哪个部分?
是循环队列的边界判断?双端队列的实现?还是实际项目中怎么选队列?
告诉我,我可以给你更针对性的代码或案例~