本篇核心知识 :顺序循环队列、链式队列、队列基本操作、STL 容器适配器(queue/deque/priority_queue)、队列与栈 / 顺序表 / 链表对比、容器底层选型、课后实操要求
一、队列基础概述
概念
队列是受限线性表 ,遵循 先进先出(FIFO, First In First Out) 规则。数据只能从队尾 (rear) 插入(入队),从队头 (front) 删除(出队),两端操作严格区分,不允许在中间位置增删元素。
特性
-
操作规则:队尾入队、队头出队,先进入的数据优先被取出;
-
分类:按存储结构分为顺序队列(数组实现) 、链式队列(链表实现);
-
核心标识:队头指针
front、队尾指针rear,用于定位首尾位置。
拓展
队列典型应用:消息排队、任务调度、广度优先搜索 (BFS)、网络请求缓冲。
二、顺序循环队列
概念
基于连续数组实现的顺序队列,为解决普通顺序队列 "假溢出" 问题,将数组首尾逻辑相连形成环形结构,称为循环队列。
1. 普通顺序队列缺陷(假溢出)
数组物理空间未用尽,但元素出队后,队头指针后移,前面空闲空间无法复用,看似队列已满,实际存在空闲区域,该现象为假溢出。循环队列通过指针循环折返,彻底解决此问题。
2. 循环队列核心设计约定
为区分队空 和队满 两种状态,主动预留一个空闲单元格:
假设数组总容量为 MAXSIZE,实际最大存储元素个数 = MAXSIZE - 1;
判空条件:front == rear(队头指针与队尾指针指向同一位置);
判满条件:(rear + 1) % MAXSIZE == front(队尾下一个位置是队头);
指针移动:入队 / 出队时,指针通过取模运算实现循环折返。
3. 成员定义与代码实现
#include <iostream>
using namespace std;
// 定义循环队列最大容量(含一个预留空位)
#define MAXSIZE 7
// 顺序循环队列
class CirQueue{
private:
int data[MAXSIZE]; // 连续数组存储元素
int front; // 队头指针:指向队首有效元素
int rear; // 队尾指针:指向队尾元素的下一个空位
public:
// 构造函数:初始化队列为空
CirQueue() : front(0), rear(0) {}
// 判断队列是否为空
bool isEmpty() const{
return front == rear;
}
// 判断队列是否已满
bool isFull() const{
return (rear + 1) % MAXSIZE == front;
}
// 入队:队尾插入元素
bool enQueue(int val){
if (isFull())
return false; // 队列已满,入队失败
data[rear] = val;
rear = (rear + 1) % MAXSIZE; // 队尾指针循环后移
return true;
}
// 出队:队头删除元素,用引用接收出队值
bool deQueue(int& val){
if (isEmpty())
return false; // 队列为空,出队失败
val = data[front];
front = (front + 1) % MAXSIZE; // 队头指针循环后移
return true;
}
// 获取队头元素(不删除)
int getFront() const{
if (!isEmpty())
return data[front];
return -1;
}
};
特性补充
-
内存特点:数组空间静态分配,初始化后容量固定,无法动态扩容;
-
优势:随机访问、操作速度快;
-
劣势:容量固定,队列满后无法新增元素;
-
取模运算
%是实现循环的核心,保证指针在0 ~ MAXSIZE-1范围内循环。
三、链式队列
概念
基于单链表实现的队列,节点动态申请内存,无固定容量限制,不会出现假溢出、队列满的问题。
特性
-
结构:由链表节点、队头指针
front、队尾指针rear组成; -
入队:在链表尾部新增节点,更新尾指针;
-
出队:删除链表头部节点,更新头指针;
-
优势:动态扩容,按需分配内存,适合数据量波动大的场景;
-
劣势:无随机访问,每个节点附带指针开销。
代码实现
#include <iostream>
using namespace std;
// 链式队列节点
struct QNode
{
int data;
QNode* next;
QNode(int val) : data(val), next(nullptr) {}
};
// 链式队列类
class LinkQueue
{
private:
QNode* front; // 队头指针
QNode* rear; // 队尾指针
public:
// 1. 普通构造函数:空队列
LinkQueue() : front(nullptr), rear(nullptr) {}
// 2. 拷贝构造函数(深拷贝)
LinkQueue(const LinkQueue& other)
{
front = nullptr;
rear = nullptr;
copyFrom(other);
}
// 3. 拷贝赋值运算符(深拷贝,防止自赋值)
LinkQueue& operator=(const LinkQueue& other)
{
// 防止自赋值:自己 = 自己
if (this == &other)
return *this;
// 先清空当前队列原有节点
clear();
// 再深拷贝目标队列
copyFrom(other);
return *this;
}
// 4. 析构函数
~LinkQueue()
{
clear();
}
// 清空队列(复用析构逻辑)
void clear()
{
while (front != nullptr)
{
QNode* temp = front;
front = front->next;
delete temp;
}
rear = nullptr;
}
// 深拷贝另一个队列的所有节点
void copyFrom(const LinkQueue& other)
{
// 原队列为空,直接返回
if (other.front == nullptr)
{
front = rear = nullptr;
return;
}
// 先拷贝队头节点
front = new QNode(other.front->data);
QNode* cur = front;
QNode* src = other.front->next;
// 依次拷贝后续所有节点
while (src != nullptr)
{
cur->next = new QNode(src->data);
cur = cur->next;
src = src->next;
}
// 尾指针指向最后一个节点
rear = cur;
}
// 判断队列是否为空
bool isEmpty() const{
return front == nullptr;
}
// 入队:尾部添加节点
void enQueue(int val){
QNode* newNode = new QNode(val);
if (isEmpty()){
// 空队列:头尾指针都指向新节点
front = newNode;
rear = newNode;
}
else{
rear->next = newNode;
rear = newNode; // 更新尾指针
}
}
// 出队:删除队头节点
bool deQueue(int& val){
if (isEmpty())
return false;
QNode* temp = front;
val = temp->data;
front = front->next;
// 若删完最后一个节点,尾指针置空
if (front == nullptr)
rear = nullptr;
delete temp;
return true;
}
// 获取队头元素
int getFront() const{
if (!isEmpty())
return front->data;
return -1;
}
};
关键易错点
-
出队时如果删除的是最后一个节点,必须将
rear置空,避免野指针; -
链式队列必须自定义析构函数,逐个释放堆节点。
四、队列分类对比
| 对比项 | 顺序循环队列 | 链式队列 |
|---|---|---|
| 底层结构 | 静态数组,内存连续 | 动态链表,内存离散 |
| 容量 | 固定,无法动态扩容 | 无上限,按需申请内存 |
| 溢出问题 | 存在队满,预留空位判状态 | 无队满、无假溢出 |
| 访问效率 | 高(数组直接访问) | 较低(只能遍历) |
| 内存开销 | 无指针额外开销 | 每个节点附带指针开销 |
| 适用场景 | 数据量固定、追求高速访问 | 数据量动态变化、不确定大小 |
五、STL 容器适配器(队列相关)
概念
容器适配器:不独立实现存储结构,基于已有基础容器封装出新容器,仅对外暴露专属接口,是 C++ STL 的核心设计思想。队列、栈、优先队列均为典型容器适配器。
5.1 std::queue 普通队列
概念
STL 标准队列,默认底层容器为deque,严格遵循先进先出。
常用成员函数
push(val):队尾入队;
pop():队头出队(无返回值);
front():获取队头元素;
back():获取队尾元素;
empty():判断是否为空;
size():获取元素个数。
代码示例
#include <iostream>
#include <queue>
using namespace std;
int main(){
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while (!q.empty()){
cout << q.front() << " ";
q.pop();
}
return 0;
}
5.2 std::deque 双向队列
概念
双端队列,支持头部、尾部两端高效插入 / 删除 ,是queue/stack默认底层容器。
特性
-
区别于
vector:vector头部增删效率极低,deque两端操作效率一致; -
底层:分段连续内存,并非单一整块数组;
-
常用接口:
push_back/push_front、pop_back/pop_front。
5.3 std::priority_queue 优先队列
概念
外观是队列,不遵循先进先出,内部自动按照优先级排序,每次出队的是优先级最高的元素(默认大顶堆)。
特性
-
底层默认基于 ** 堆(heap)** 实现;
-
应用场景:任务优先级调度、TOP-K 问题、排序优化;
-
接口与普通
queue基本一致。
代码示例
#include <iostream>
#include <queue>
using namespace std;
int main(){
// 默认大顶堆:数值越大优先级越高
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(5);
while (!pq.empty()){
cout << pq.top() << " ";
pq.pop();
}
// 输出:5 3 1
return 0;
}
拓展:容器适配器自定义底层
STL 队列可手动指定底层容器,支持deque/list:
// 指定以list作为queue底层容器
queue<int, list<int>> q;
六、队列与栈、顺序表、链表综合对比
-
栈 VS 队列
栈:先进后出,仅在同一端增删;
队列:先进先出,两端区分队头 / 队尾。
-
vector VS deque
vector:连续数组,尾部操作快,头部增删需要挪动元素,效率差;
deque:分段内存,头尾增删效率都很高。
-
原生手写队列 VS STL 队列
手写:理解底层原理,面试手写代码必考;
STL:工程开发直接使用,无需管理内存,开发效率高。