前言
队列和栈一样,属于受限线性表 。栈是后进先出 LIFO,队列则是先进先出 FIFO(First‑In‑First‑Out)。日常开发中消息队列、任务缓冲、广度优先搜索 BFS 都离不开队列。本文按照学习路线:队列概念→链式存储→顺序存储→循环队列力扣真题→双端队列完整梳理
1 队列的基本概念
队列(Queue):只允许一端插入,另一端删除的线性表。
- 队尾 (rear):执行插入(入队)的一端
- 队头 (front):执行删除(出队)的一端
- 规则:先入队的元素,必须先出队。不允许直接访问中间元素。
常见操作:
enqueue(x):入队,队尾插入元素 xdequeue():出队,删除队头元素front():读取队头元素isEmpty():判断队列是否为空size():获取队列元素数量
应用场景:任务排队、消息缓冲、BFS 广度优先遍历、打印机任务调度。
📌栈:入口出口同一个端;队列:入口出口分开两端。
对于一般的队列我们采用单链表的存储方式
2 队列的链式存储(链队)
链式队列,用单链表实现队列。为了入队出队都做到 O (1) 效率,维护两个指针:
front:队头指针,指向链表头部rear:队尾指针,指向链表尾部节点
优点:没有假溢出,动态分配内存,几乎不会队满(内存耗尽除外)
缺点:每个节点需要额外 next 指针,内存开销大;不能随机访问。
代码
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType; // 队列中存储数据元素的类型
// 采用单链表实现队列,链表结点的定义
typedef struct LinkNode {
QDataType data;
struct LinkNode* next;
}QNode;
typedef struct {
QNode* front;
QNode* rear;
int size;
}LinkQueue;
// 初始化队列
void QueueInit(LinkQueue* q);
// 销毁队列
void QueueDestroy(LinkQueue* q);
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q);
// 获取队头元素
QDataType QueueFront(LinkQueue* q);
// 将x入队列
void EnQueue(LinkQueue* q, QDataType x);
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q);
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
// 初始化队列
void QueueInit(LinkQueue* q)
{
assert(q);
q->front = q->rear = (QNode*)malloc(sizeof(QNode));
q->size = 0;
q->rear->next = NULL;
}
// 销毁队列
void QueueDestroy(LinkQueue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
cur=NULL;
q->front = q->rear = NULL;
q->size = 0;
}
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q) {
assert(q);
return q->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q){
assert(q);
return q->size;
}
// 获取队头元素
QDataType QueueFront(LinkQueue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->next->data;
}
// 将x入队列
void EnQueue(LinkQueue* q, QDataType x){
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
newnode->data = x;
newnode->next = NULL;
q->rear->next = newnode;
q->rear = newnode;
q->size++;
}
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q){
assert(q);
assert(!QueueEmpty(q));
QDataType ret = q->front->next->data;
QNode* del = q->front->next;
q->front->next = del->next;
free(del);
q->size--;
if (q->front->next == NULL)
{
q->rear = q->front;
}
return ret;
}
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
int main() {
LinkQueue lq;
QueueInit(&lq);
EnQueue(&lq, 1);
EnQueue(&lq, 2);
EnQueue(&lq, 3);
EnQueue(&lq, 4);
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
EnQueue(&lq, 5);
EnQueue(&lq, 6);
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
QueueDestroy(&lq);
return 0;
}
3.队列的顺序存储 & 循环队列
顺序队列:底层数组实现,维护 front、rear 下标。 普通顺序队列有严重问题:假溢出。数组前面出队空出一大片空间,但 rear 已经走到数组末尾,明明还有空闲空间,却不能入队。
解决方案:循环队列 。逻辑上把数组头尾相连形成环,用取模%运算实现下标回绕。
循环队列两个经典方案
方案 1:牺牲一个存储单元区分空 / 满(考研重点)
- 判空:
front == rear - 判满:
(rear + 1) % MaxSize == front - 队列有效元素个数公式:
(rear - front + MaxSize) % MaxSize
数组实际最多存放 MaxSize‑1 个元素,留一个空位用来区分队空和队满两种状态,避免逻辑歧义。
方案 2:增加 size 计数器(工程更常用)
结构体增加size记录元素个数:
- 判空:
size == 0 - 判满:
size == MaxSize不需要牺牲数组空间,逻辑简单,LeetCode622 就可以用这种写法。
循环队列入队:rear = (rear +1)%MaxSize 循环队列出队:front = (front +1)%MaxSize
优点:数组实现,访问速度快,空间利用率高,解决假溢出。 缺点:容量固定。
如果采用牺牲一格版本,数组开辟 k+1 大小,判满条件
(rear+1)%cap == front,不需要 size 变量
cpp
class MyCircularQueue {
public:
/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue* obj = new MyCircularQueue(k);
* bool param_1 = obj->enQueue(value);
* bool param_2 = obj->deQueue();
* int param_3 = obj->Front();
* int param_4 = obj->Rear();
* bool param_5 = obj->isEmpty();
* bool param_6 = obj->isFull();
*/
MyCircularQueue(int k) {
_ringqueue.assign(k, 0);
_size = 0;
_front=_rear=0;
_cap = k;
}
bool isEmpty() { return _size == 0; }
bool isFull() { return _size == _cap ; }
bool enQueue(int value) {
if (isFull())
return false;
_ringqueue[_rear] = value;
_rear = (_rear + 1) % _cap;
_size++;
return true;
}
bool deQueue() {
if (isEmpty())
return false;
_front = (_front + 1 + _cap) % _cap;
_size--;
return true;
}
int Front() {
if (isEmpty())
return -1;
int ret = _ringqueue[_front];
return ret;
}
int Rear() {
if (isEmpty())
return -1;
int tmp = (_rear - 1 + _cap) % _cap;
int ret = _ringqueue[tmp];
return ret;
}
private:
int _size;
vector<int> _ringqueue;
int _front;
int _rear;
int _cap;
};
Rear()获取队尾元素:rear 指向下一个要写入的空位 ,真实队尾是(rear‑1+capacity)%capacity,加 capacity 防止负数取模出错。- 所有下标移动必须做取模,实现环形回绕。
- 函数返回 bool,操作失败返回 false;空队列取元素返回‑1。
| 存储方式 | 优点 | 缺点 | 判空 | 判满 |
|---|---|---|---|---|
| 链式队列 | 动态扩容,无假溢出 | 指针开销 | front==rear | 内存耗尽才满 |
| 普通顺序队列 | 数组速度快 | 假溢出 | front==rear | rear 到达数组末尾 |
| 循环队列 | 数组、解决假溢出 | 容量固定 | front==rear / size==0 | (rear+1)%max==front / size==max |