1. 什么是队列
队列(Queue)是一种先进先出(FIFO,First In First Out)的线性数据结构。数据从一端(队尾)进入,从另一端(队头)离开,就像日常生活中的排队一样:先来的人先被服务,后来的人排在队尾等待。
队列只允许在队尾进行插入操作(入队,enqueue),在队头进行删除操作(出队,dequeue)。这种受限的访问方式保证了数据处理的公平性和顺序性。
队列的核心操作包括:
- 入队(enqueue):在队尾添加一个元素。
- 出队(dequeue):从队头移除一个元素。
- 队头(front):查看队头元素,但不移除。
- 队尾(rear):查看队尾元素,但不移除。
- 判空(isEmpty):判断队列是否为空。
- 队列长度(size):返回队列中元素的个数。
2. 队列的分类
根据底层实现和功能扩展,队列主要分为以下几类:
2.1 顺序队列(数组实现)
顺序队列使用数组作为底层存储结构,通过两个指针(队头指针 front 和队尾指针 rear)来标记队列的边界。入队时 rear 后移,出队时 front 后移。
顺序队列的缺点是存在假溢出问题:当 rear 到达数组末尾时,即使数组前部还有空闲空间,也无法继续入队。
2.2 循环队列(环形队列)
循环队列是对顺序队列的改进,它将数组首尾相连,形成一个环形结构。当 rear 到达数组末尾时,如果数组前部有空闲空间,rear 会绕回到数组开头继续使用,从而解决假溢出问题。
循环队列通常牺牲一个存储单元来区分队空和队满状态,或者使用一个计数器来记录元素个数。
2.3 链式队列(链表实现)
链式队列使用链表作为底层存储结构,每个节点包含数据和指向下一个节点的指针。链式队列不存在容量限制,可以动态增长,但每个节点需要额外的指针存储空间。
2.4 双端队列(Deque)
双端队列(Double-Ended Queue)允许在队头和队尾两端都进行插入和删除操作,兼具栈和队列的特性。双端队列又分为输出受限和输入受限两种变体。
2.5 优先队列(Priority Queue)
优先队列不再遵循严格的 FIFO 原则,而是根据元素的优先级决定出队顺序。优先级最高的元素最先出队。优先队列通常使用堆(Heap)数据结构实现,入队和出队的时间复杂度为 O(log n)。
2.6 阻塞队列(Blocking Queue)
阻塞队列是并发编程中常用的线程安全队列。当队列为空时,出队操作会被阻塞,直到有元素入队;当队列满时,入队操作会被阻塞,直到有元素出队。阻塞队列常用于生产者-消费者模型。
3. 各类队列的作用与应用场景
3.1 顺序队列和循环队列
顺序队列和循环队列适用于元素数量可预估 、操作频繁的场景。由于数组具有连续内存和缓存友好的特性,在元素数量不大时性能优于链式队列。
典型应用场景:
- 操作系统中的进程调度队列。
- 网络数据包的缓冲队列。
- 消息队列的底层存储。
3.2 链式队列
链式队列适用于元素数量动态变化大 、无法预估上限的场景。它不受固定容量限制,可以灵活扩展。
典型应用场景:
- 任务调度系统中的待处理任务队列。
- 图的广度优先搜索(BFS)中使用的队列。
- 日志系统中的异步写入队列。
3.3 双端队列
双端队列适用于需要在两端灵活操作的场景,例如实现撤销/重做功能、滑动窗口算法等。
典型应用场景:
- 浏览器的前进后退功能。
- 滑动窗口最大值问题。
- 实现栈和队列的混合数据结构。
3.4 优先队列
优先队列适用于需要按优先级处理任务的场景,例如任务调度、事件驱动模拟等。
典型应用场景:
- 操作系统的进程调度(按优先级)。
- Dijkstra 最短路径算法。
- 哈夫曼编码。
- 实时系统中的紧急任务处理。
3.5 阻塞队列
阻塞队列适用于多线程并发场景,用于线程间的安全数据传递和任务分配。
典型应用场景:
- 生产者-消费者模型。
- 线程池的任务队列。
- 分布式系统中的消息中间件。
4. 队列与栈的区别
栈(Stack)是一种后进先出(LIFO,Last In First Out)的线性数据结构,而队列是先进先出(FIFO)。两者的核心区别如下:
| 对比维度 | 队列(Queue) | 栈(Stack) |
|---|---|---|
| 数据访问顺序 | 先进先出(FIFO) | 后进先出(LIFO) |
| 插入操作 | 入队(enqueue),在队尾插入 | 入栈(push),在栈顶插入 |
| 删除操作 | 出队(dequeue),从队头删除 | 出栈(pop),从栈顶删除 |
| 操作端 | 两端操作(队头删除、队尾插入) | 单端操作(只在栈顶操作) |
| 典型应用 | 任务调度、消息队列、BFS | 函数调用、表达式求值、DFS |
| 底层实现 | 数组或链表 | 数组或链表 |
简单来说,队列是先进先出 ,栈是后进先出。队列适合处理需要按顺序公平处理的场景,而栈适合处理需要回溯或嵌套的场景。
5. C/C++ 代码实现
5.1 顺序队列(C 语言实现)
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} SeqQueue;
// 初始化队列
void initQueue(SeqQueue *q) {
q->front = 0;
q->rear = 0;
}
// 判断队列是否为空
bool isEmpty(SeqQueue *q) {
return q->front == q->rear;
}
// 判断队列是否已满
bool isFull(SeqQueue *q) {
return q->rear == MAX_SIZE;
}
// 入队
bool enqueue(SeqQueue *q, int value) {
if (isFull(q)) {
printf("队列已满,无法入队\n");
return false;
}
q->data[q->rear++] = value;
return true;
}
// 出队
bool dequeue(SeqQueue *q, int *value) {
if (isEmpty(q)) {
printf("队列为空,无法出队\n");
return false;
}
*value = q->data[q->front++];
return true;
}
// 查看队头元素
bool peek(SeqQueue *q, int *value) {
if (isEmpty(q)) {
printf("队列为空\n");
return false;
}
*value = q->data[q->front];
return true;
}
int main() {
SeqQueue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
int value;
while (dequeue(&q, &value)) {
printf("出队元素: %d\n", value);
}
return 0;
}
5.2 循环队列(C 语言实现)
c
#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 5
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} CircularQueue;
// 初始化循环队列
void initQueue(CircularQueue *q) {
q->front = 0;
q->rear = 0;
}
// 判断队列是否为空
bool isEmpty(CircularQueue *q) {
return q->front == q->rear;
}
// 判断队列是否已满(牺牲一个存储单元)
bool isFull(CircularQueue *q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
// 入队
bool enqueue(CircularQueue *q, int value) {
if (isFull(q)) {
printf("循环队列已满,无法入队\n");
return false;
}
q->data[q->rear] = value;
q->rear = (q->rear + 1) % MAX_SIZE;
return true;
}
// 出队
bool dequeue(CircularQueue *q, int *value) {
if (isEmpty(q)) {
printf("循环队列为空,无法出队\n");
return false;
}
*value = q->data[q->front];
q->front = (q->front + 1) % MAX_SIZE;
return true;
}
int main() {
CircularQueue q;
initQueue(&q);
enqueue(&q, 1);
enqueue(&q, 2);
enqueue(&q, 3);
enqueue(&q, 4);
int value;
while (dequeue(&q, &value)) {
printf("出队元素: %d\n", value);
}
return 0;
}
5.3 链式队列(C++ 实现)
cpp
#include <iostream>
template <typename T>
class LinkedQueue {
private:
struct Node {
T data;
Node *next;
Node(const T &value) : data(value), next(nullptr) {}
};
Node *front;
Node *rear;
int count;
public:
LinkedQueue() : front(nullptr), rear(nullptr), count(0) {}
~LinkedQueue() {
while (!isEmpty()) {
T value;
dequeue(value);
}
}
bool isEmpty() const {
return front == nullptr;
}
int size() const {
return count;
}
void enqueue(const T &value) {
Node *newNode = new Node(value);
if (isEmpty()) {
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
count++;
}
bool dequeue(T &value) {
if (isEmpty()) {
return false;
}
Node *temp = front;
value = temp->data;
front = front->next;
if (front == nullptr) {
rear = nullptr;
}
delete temp;
count--;
return true;
}
bool peek(T &value) const {
if (isEmpty()) {
return false;
}
value = front->data;
return true;
}
};
int main() {
LinkedQueue<int> q;
q.enqueue(100);
q.enqueue(200);
q.enqueue(300);
int value;
while (q.dequeue(value)) {
std::cout << "出队元素: " << value << std::endl;
}
return 0;
}
5.4 双端队列(C++ STL deque)
cpp
#include <iostream>
#include <deque>
int main() {
std::deque<int> dq;
// 在队尾插入
dq.push_back(10);
dq.push_back(20);
// 在队头插入
dq.push_front(5);
dq.push_front(1);
std::cout << "队头元素: " << dq.front() << std::endl;
std::cout << "队尾元素: " << dq.back() << std::endl;
// 从队头删除
dq.pop_front();
// 从队尾删除
dq.pop_back();
std::cout << "当前队列大小: " << dq.size() << std::endl;
return 0;
}
5.5 优先队列(C++ STL priority_queue)
cpp
#include <iostream>
#include <queue>
#include <vector>
int main() {
// 默认是大顶堆,优先级最高的元素(最大值)先出队
std::priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);
std::cout << "优先队列出队顺序(大顶堆): ";
while (!pq.empty()) {
std::cout << pq.top() << " ";
pq.pop();
}
std::cout << std::endl;
// 小顶堆:使用 greater 比较器
std::priority_queue<int, std::vector<int>, std::greater<int>> minPq;
minPq.push(30);
minPq.push(10);
minPq.push(50);
minPq.push(20);
std::cout << "优先队列出队顺序(小顶堆): ";
while (!minPq.empty()) {
std::cout << minPq.top() << " ";
minPq.pop();
}
std::cout << std::endl;
return 0;
}
5.6 阻塞队列(C++ 实现,基于互斥锁和条件变量)
cpp
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
template <typename T>
class BlockingQueue {
private:
std::queue<T> queue;
std::mutex mtx;
std::condition_variable notEmpty;
std::condition_variable notFull;
size_t capacity;
public:
BlockingQueue(size_t cap) : capacity(cap) {}
void push(const T &value) {
std::unique_lock<std::mutex> lock(mtx);
notFull.wait(lock, [this]() { return queue.size() < capacity; });
queue.push(value);
notEmpty.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(mtx);
notEmpty.wait(lock, [this]() { return !queue.empty(); });
T value = queue.front();
queue.pop();
notFull.notify_one();
return value;
}
bool empty() {
std::lock_guard<std::mutex> lock(mtx);
return queue.empty();
}
};
int main() {
BlockingQueue<int> bq(3);
// 生产者线程
std::thread producer([&bq]() {
for (int i = 1; i <= 5; i++) {
bq.push(i);
std::cout << "生产者入队: " << i << std::endl;
}
});
// 消费者线程
std::thread consumer([&bq]() {
for (int i = 1; i <= 5; i++) {
int value = bq.pop();
std::cout << "消费者出队: " << value << std::endl;
}
});
producer.join();
consumer.join();
return 0;
}
6. 总结
队列是一种先进先出的线性数据结构,根据底层实现和功能扩展可分为顺序队列、循环队列、链式队列、双端队列、优先队列和阻塞队列等类型。每种队列都有其特定的适用场景:顺序队列和循环队列适合容量可预估的场景,链式队列适合动态增长的场景,双端队列适合两端操作的场景,优先队列适合按优先级处理的场景,阻塞队列适合多线程并发场景。
队列与栈的核心区别在于数据访问顺序:队列是先进先出(FIFO),栈是后进先出(LIFO)。理解这两种数据结构的区别,有助于在实际开发中根据业务需求选择合适的数据结构。