加载中…
应用场景:BFS · 层序遍历 · 单调队列 · 任务调度
交互式动画演示,可调整参数并单步执行。
全屏打开队列(Queue) 是一种先进先出(FIFO, First In First Out) 的线性数据结构。
像排队买票:先到的人先被服务。
graph LR E1[enqueue A] --> Q[(队列)] E2[enqueue B] --> Q E3[enqueue C] --> Q Q -->|dequeue A| D1[返回 A] Q -->|dequeue B| D2[返回 B]
| 操作 | 说明 | 复杂度 |
|---|---|---|
enqueue | 入队(从队尾添加) | O(1) |
dequeue | 出队(从队首移除) | O(1) |
front / peek | 查看队首 | O(1) |
rear | 查看队尾 | O(1) |
isEmpty / size | 判空 / 元素数 | O(1) |
public class ArrayQueue<E> {
private ArrayList<E> data = new ArrayList<>();
public void enqueue(E e) { data.add(e); }
public E dequeue() {
if (data.isEmpty()) throw new NoSuchElementException();
return data.remove(0); // ❌ O(n)
}
}问题:
remove(0)涉及数组搬移,每次出队 O(n)。
循环队列通过 front 和 tail 指针 + 模运算,实现 O(1) 的入队和出队。
public class LoopQueue<E> {
private E[] data;
private int front, tail, size;
@SuppressWarnings("unchecked")
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity];
front = tail = size = 0;
}
public void enqueue(E e) {
if (size == data.length) resize(data.length * 2);
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
public E dequeue() {
if (isEmpty()) throw new NoSuchElementException();
E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
if (size <= data.length / 4 && data.length > 1) resize(data.length / 2);
return ret;
}
public E getFront() {
if (isEmpty()) throw new NoSuchElementException();
return data[front];
}
public int getSize() { return size; }
public boolean isEmpty() { return size == 0; }
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
}关键点:
(i + front) % data.length 把环形索引转成线性数组下标。public class LinkedQueue<E> {
private Node<E> head, tail;
private int size;
private static class Node<E> { E val; Node<E> next; Node(E v) { val = v; } }
public void enqueue(E e) {
Node<E> node = new Node<>(e);
if (tail == null) head = tail = node;
else { tail.next = node; tail = node; }
size++;
}
public E dequeue() {
if (head == null) throw new NoSuchElementException();
E v = head.val;
head = head.next;
if (head == null) tail = null;
size--;
return v;
}
}| 实现 | 入队 | 出队 | 空间 |
|---|---|---|---|
| 数组(无循环) | O(1) | O(n) | 连续 |
| 循环数组 | O(1) 均摊 | O(1) 均摊 | 连续 |
| 链表 | O(1) | O(1) | 离散 |
Java 推荐
ArrayDeque(基于循环数组)或LinkedList(基于链表)。
两端都能入队/出队。Java 用 ArrayDeque 或 LinkedList。
Deque<Integer> dq = new ArrayDeque<>();
dq.offerFirst(1); // 队首入队
dq.offerLast(2); // 队尾入队
dq.pollFirst(); // 队首出队
dq.pollLast(); // 队尾出队元素按优先级出队,而非 FIFO。底层通常用堆实现。
PriorityQueue<Integer> minPQ = new PriorityQueue<>(); // 最小堆
PriorityQueue<Integer> maxPQ = new PriorityQueue<>((a, b) -> b - a); // 最大堆见"堆与优先队列"专题。
线程安全,队列为空时 take 会阻塞。常用于生产者-消费者模式。
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1024);
queue.put(1); // 满时阻塞
int x = queue.take(); // 空时阻塞void bfs(Node start) {
Deque<Node> q = new ArrayDeque<>();
Set<Node> visited = new HashSet<>();
q.offer(start); visited.add(start);
while (!q.isEmpty()) {
Node u = q.poll();
for (Node v : u.neighbors) {
if (!visited.contains(v)) {
visited.add(v);
q.offer(v);
}
}
}
}操作系统任务调度、消息中间件(Kafka、RocketMQ)的核心。
见"单调栈与单调队列"专题。
class MyStack {
Deque<Integer> q = new ArrayDeque<>();
public void push(int x) {
q.offer(x);
for (int i = 1; i < q.size(); i++) q.offer(q.poll());
}
public int pop() { return q.poll(); }
public int top() { return q.peek(); }
public boolean empty() { return q.isEmpty(); }
}每次 push 后,把队列其他元素轮转一遍,让新元素到队首。
复杂度:push O(n),pop O(1) 均摊。
| 操作 | 时间 | 空间 |
|---|---|---|
| enqueue | O(1) 均摊 | O(1) |
| dequeue | O(1) 均摊 | O(1) |
| front | O(1) | O(1) |
LeetCode 622:设计循环队列。
class MyCircularQueue {
int[] data;
int front, tail, size;
public MyCircularQueue(int k) {
data = new int[k];
}
public boolean enQueue(int v) {
if (size == data.length) return false;
data[tail] = v;
tail = (tail + 1) % data.length;
size++;
return true;
}
public boolean deQueue() {
if (size == 0) return false;
data[front] = 0;
front = (front + 1) % data.length;
size--;
return true;
}
public int Front() { return size == 0 ? -1 : data[front]; }
public int Rear() { return size == 0 ? -1 : data[(tail - 1 + data.length) % data.length]; }
public boolean isEmpty() { return size == 0; }
public boolean isFull() { return size == data.length; }
}关键技巧:(tail - 1 + n) % n 取队尾元素。
dequeue 时要先检查 head。(tail - 1 + n) % n,别直接 data[tail-1]。Queue 接口:注意区分 add(抛异常)vs offer(返回 false)。| 难度 | 题目 | 类型 |
|---|---|---|
| 🟢 | 用队列实现栈 | 双队列 |
| 🟢 | 用栈实现队列 | 双栈 |
| 🟡 | 设计循环队列 | 循环数组 |
| 🟡 | 滑动窗口最大值 | 单调队列 |
| 🟡 | 二叉树的层序遍历 | BFS |
| 🟠 | 数据流的中位数 | 双堆 |
| 🟠 | 任务调度器 | 优先队列 |
| 🔴 | 滑动窗口中位数 | 平衡树 |
队列的本质:让"先到的先处理"。
看到"按时间顺序 / 层层扩散 / 排队"这些关键词,99% 是队列。