队列
最后更新
队列有两个活动的端口。新值从队尾加入,值从队头离开,所以等得最久的那个最先被处理。这就是先进先出(FIFO),和柜台前排队完全一样:从队尾加入、从队头接受服务,等待才公平。点击上方的“播放”,观看值从一侧进入、从另一侧离开。
因为两端各有自己的下标或指针,两种操作都是 O(1),都不需要移动其余数据。所以凡是按到达顺序处理任务的地方,底层都有队列:打印任务、任务队列和消息队列、请求缓冲区,还有广度优先搜索,它正是因为把待访问的边界存放在队列里,才能逐层访问图。把删除的一端移到队尾,你得到的就是栈。
时间复杂度与空间复杂度
以下是基于环形缓冲区或链表实现的队列,这是两种标准实现方式:
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 入队(enqueue) | O(1) | 在队尾写入,并把队尾下标向前移动。 |
| 出队(dequeue) | O(1) | 在队头读取,并把队头下标向前移动,不需要移动元素。 |
| 查看队头(peek) | O(1) | 读取队头的值,但不删除它。 |
| 查找 | O(n) | 这不是队列的用途:要看到里面的内容,必须把它清空。 |
| 空间 | O(n) | 每个等待中的值占一个位置。 |
分步说明
| 步骤 | 发生了什么 |
|---|---|
| 1 | 队列一开始是空的,队头和队尾指向同一个位置。 |
| 2 | 入队时把值写到队尾,然后队尾向前移动一位。 |
| 3 | 之后每次入队,新值都排在已经在等待的值后面。 |
| 4 | 出队时读取队头的值,然后队头向前移动一位。 |
| 5 | 取出的值永远是等待时间最长的那个。 |
| 6 | 当队头追上队尾时,队列再次为空,这时继续出队就会出错。 |
实例演示
依次让 3、7、5 入队,然后把队列清空:
| 操作 | 队列(从队头到队尾) | 返回值 |
|---|---|---|
enqueue(3) | [3] | 无 |
enqueue(7) | [3, 7] | 无 |
enqueue(5) | [3, 7, 5] | 无 |
dequeue() | [7, 5] | 3,最早加入的值 |
dequeue() | [5] | 7 |
dequeue() | [] | 5,最新加入的值,最后取出 |
什么时候用队列
Queue 代码
简洁、可运行的 Queue 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。
Queue 的 Python 代码
Python
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7 queue.append(value)8 print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12 value = queue.popleft()13 print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)Queue 的 JavaScript 代码
JavaScript
1// A plain array makes dequeue O(n): shift() moves every element left.2// Track a head index instead, the fix the queue article describes.3const queue = { items: [], head: 0 };4
5function enqueue(value) {6 queue.items.push(value);7}8
9function dequeue() {10 const value = queue.items[queue.head];11 queue.items[queue.head] = undefined; // free the slot12 queue.head += 1;13 // Reclaim space once the consumed prefix dominates.14 if (queue.head * 2 >= queue.items.length) {15 queue.items = queue.items.slice(queue.head);16 queue.head = 0;17 }18 return value;19}20
21const size = () => queue.items.length - queue.head;22
23for (const value of [3, 7, 5]) {24 enqueue(value);25 console.log(`enqueue ${value} -> size ${size()}`);26}27
28// Dequeue from the front: first in, first out, amortized O(1)29while (size() > 0) {30 console.log(`dequeue ${dequeue()} -> size ${size()}`);31}32
33console.log('empty:', size() === 0);Queue 的 Java 代码
Java
1import java.util.ArrayDeque;2import java.util.Queue;3
4public class Main {5 public static void main(String[] args) {6 Queue<Integer> queue = new ArrayDeque<>();7
8 // Enqueue three values at the rear9 for (int value : new int[] {3, 7, 5}) {10 queue.add(value);11 System.out.println("enqueue " + value + " -> " + queue);12 }13
14 // Dequeue them from the front: first in, first out15 while (!queue.isEmpty()) {16 int value = queue.remove();17 System.out.println("dequeue " + value + " -> " + queue);18 }19
20 System.out.println("empty: " + queue.isEmpty());21 }22}Queue 的 C++ 代码
C++
1#include <iostream>2#include <queue>3
4int main() {5 std::queue<int> queue;6
7 // Enqueue three values at the rear8 for (int value : {3, 7, 5}) {9 queue.push(value);10 std::cout << "enqueue " << value << " -> size " << queue.size() << "\n";11 }12
13 // Dequeue them from the front: first in, first out14 while (!queue.empty()) {15 int value = queue.front();16 queue.pop();17 std::cout << "dequeue " << value << " -> size " << queue.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << queue.empty() << "\n";21 return 0;22}Queue 的 C 代码
C
1#include <stdio.h>2
3#define CAP 164
5int queue[CAP];6int front = 0;7int rear = 0; /* index of the next free slot */8
9int main(void) {10 int values[3] = {3, 7, 5};11
12 /* Enqueue three values at the rear */13 for (int i = 0; i < 3; i++) {14 queue[rear++] = values[i];15 printf("enqueue %d -> size %d\n", values[i], rear - front);16 }17
18 /* Dequeue them from the front: first in, first out */19 while (front < rear) {20 int value = queue[front++];21 printf("dequeue %d -> size %d\n", value, rear - front);22 }23
24 printf("empty: %d\n", front == rear);25 return 0;26}队列常见问题
FIFO 是什么意思?
先进先出(First In, First Out):等待最久的值最先被处理。售票窗口前的队伍就是日常生活中的例子。栈则遵循相反的规则,即后进先出(LIFO)。
队列和栈有什么区别?
区别只在于从哪一端删除。两者都以
O(1) 在末尾添加;队列从队头删除(FIFO),栈从添加的同一端删除(LIFO)。除此之外,它们的复杂度表完全相同。队列有哪些主要操作?
enqueue 在队尾添加一个值,dequeue 删除并返回队头的值,peek(或 front)读取队头但不删除,is_empty 判断是否还有元素在等待。这四个操作都是 O(1)。为什么用普通数组实现时出队很慢?
因为从数组中删除下标 0 的元素会让其余所有元素向左移动,每次出队都变成
O(n)。实际的实现会用移动队头下标的环形缓冲区,或者带头指针的链表来避免这个问题。Python 的 collections.deque 和 Java 的 ArrayDeque 已经替你做好了,而 list.pop(0) 没有。循环队列是什么?
循环队列建立在固定大小的数组上,队头和队尾下标走到末尾时会绕回 0。它会重复利用出队后空出来的位置,所以容量为
n 的队列可以一直工作下去,而不会走出数组的末尾。实际程序中哪里会用到队列?
服务之间的任务队列和消息队列、打印和作业排队、Web 服务器中的请求缓冲区、键盘和事件缓冲区、生产者消费者管道,还有广度优先搜索,正是队列让它能够逐层遍历。