队列合集
优先队列
在 C++ 中,priority_queue(优先队列)是一种容器适配器,它提供了一种按照优先级自动排序的队列功能。与普通队列(queue)的“先进先出(FIFO)”不同,priority_queue 中每次出队的元素都是当前队列中优先级最高的元素。
核心特性
- 默认排序:默认情况下,
priority_queue采用大顶堆(max-heap) 结构,即每次出队的是最大的元素(数值越大优先级越高)。 - 底层容器:默认基于
vector实现(也可指定为deque),通过堆算法维护元素的优先级顺序。 - 不可直接访问中间元素:只能访问队顶(优先级最高)的元素,不支持迭代器遍历。
头文件与定义
使用 priority_queue 需要包含头文件 <queue>,定义格式如下:
#include <queue>
using namespace std;
// 默认:大顶堆(元素类型为 T,底层容器为 vector,比较器为 less<T>)
priority_queue<T> pq;
// 自定义:指定底层容器和比较器(例如小顶堆)
priority_queue<T, Container, Compare> pq;
常用操作
| 操作 | 功能说明 | 时间复杂度 |
|---|---|---|
pq.push(val) |
插入元素 val 到队列,并维护优先级 |
O(log n) |
pq.pop() |
移除队顶(优先级最高)的元素 | O(log n) |
pq.top() |
返回队顶元素的引用(不删除) | O(1) |
pq.empty() |
判断队列是否为空(空返回 true) |
O(1) |
pq.size() |
返回队列中元素的个数 | O(1) |
示例:默认大顶堆
#include <iostream>
#include <queue>
using namespace std;
int main() {
priority_queue<int> pq;
// 插入元素
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);
// 输出队顶并移除,直到为空
while (!pq.empty()) {
cout << pq.top() << " "; // 每次输出当前最大元素
pq.pop();
}
// 输出:50 30 20 10
return 0;
}
自定义优先级(小顶堆)
如果需要小顶堆(每次出队最小元素),可以通过指定比较器 greater<T> 实现:
#include <iostream>
#include <queue>
#include <vector> // 需显式指定底层容器
using namespace std;
int main() {
// 小顶堆:元素类型 int,底层容器 vector,比较器 greater<int>
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);
while (!pq.empty()) {
cout << pq.top() << " "; // 每次输出当前最小元素
pq.pop();
}
// 输出:10 20 30 50
return 0;
}
自定义类型的优先级
对于自定义结构体/类,需要通过重载运算符或自定义比较器指定优先级:
方法1:重载 < 运算符(用于默认大顶堆)
#include <iostream>
#include <queue>
using namespace std;
struct Person {
string name;
int age;
// 重载 <:年龄大的优先级高(用于大顶堆)
bool operator<(const Person& other) const {
return age < other.age; // 注意:堆内部用 < 比较,返回 true 表示当前对象优先级低
}
};
int main() {
priority_queue<Person> pq;
pq.push({"Alice", 25});
pq.push({"Bob", 30});
pq.push({"Charlie", 20});
while (!pq.empty()) {
cout << pq.top().name << "(" << pq.top().age << "), ";
pq.pop();
}
// 输出:Bob(30), Alice(25), Charlie(20),
return 0;
}
方法2:自定义比较器(灵活控制优先级)
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct Person {
string name;
int age;
};
// 自定义比较器:年龄小的优先级高(用于小顶堆)
struct CompareAge {
bool operator()(const Person& a, const Person& b) {
return a.age > b.age; // 注意:返回 true 表示 a 优先级低于 b
}
};
int main() {
priority_queue<Person, vector<Person>, CompareAge> pq;
pq.push({"Alice", 25});
pq.push({"Bob", 30});
pq.push({"Charlie", 20});
while (!pq.empty()) {
cout << pq.top().name << "(" << pq.top().age << "), ";
pq.pop();
}
// 输出:Charlie(20), Alice(25), Bob(30),
return 0;
}
总结
priority_queue适用于需要动态获取最大/最小元素的场景(如任务调度、贪心算法等)。- 默认是大顶堆,通过
greater<T>可改为小顶堆。 - 自定义类型需通过重载运算符或比较器指定优先级规则。
- 不支持随机访问,仅能操作队顶元素,插入和删除的时间复杂度为 O(log n)。
在 C++ 中,priority_queue(优先队列)和 queue(普通队列)都是容器适配器,但它们的核心行为和应用场景有显著区别,主要体现在元素的访问和移除顺序上。
1. 核心特性对比
| 特性 | queue(普通队列) |
priority_queue(优先队列) |
|---|---|---|
| 元素顺序规则 | 遵循“先进先出(FIFO)”原则:先插入的元素先被移除。 | 遵循“优先级最高先出”原则:每次移除的是当前队列中优先级最高的元素(与插入顺序无关)。 |
| 底层数据结构 | 默认基于 deque 实现(也可指定 list)。 |
默认基于 vector 实现(也可指定 deque),内部通过堆算法维护优先级。 |
| 访问权限 | 只能访问队头(front)和队尾(back)元素。 | 只能访问优先级最高的元素(队顶,top),无法直接访问其他元素(包括队尾)。 |
| 典型应用场景 | 需按插入顺序处理元素的场景(如广度优先搜索 BFS、缓存队列、任务排队等)。 | 需动态获取“最大/最小元素”的场景(如贪心算法、任务调度、TOP-K 问题等)。 |
2. 操作上的差异
两者的基本操作(push、pop、empty、size)名称相同,但行为逻辑不同:
-
push操作:queue:将元素插入队尾,不改变其他元素顺序。priority_queue:将元素插入后,会通过堆调整重新排序,确保队顶始终是优先级最高的元素。
-
pop操作:queue:移除队头(最先插入的元素)。priority_queue:移除队顶(当前优先级最高的元素),并重新调整堆结构维持优先级。
-
元素访问:
queue用front()获取队头元素,back()获取队尾元素。priority_queue只能用top()获取优先级最高的元素(无front或back)。
3. 示例对比
普通队列(queue)示例
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
q.push(3); // 队尾:3
q.push(1); // 队尾:1(队列:3, 1)
q.push(2); // 队尾:2(队列:3, 1, 2)
while (!q.empty()) {
cout << q.front() << " "; // 依次输出队头(先入先出)
q.pop();
}
// 输出:3 1 2
return 0;
}
优先队列(priority_queue)示例
#include <iostream>
#include <queue>
using namespace std;
int main() {
priority_queue<int> pq; // 默认大顶堆(最大值优先)
pq.push(3);
pq.push(1);
pq.push(2);
while (!pq.empty()) {
cout << pq.top() << " "; // 依次输出优先级最高的元素(最大的)
pq.pop();
}
// 输出:3 2 1
return 0;
}
4. 总结
queue是“顺序依赖”的容器,适合按插入顺序处理元素,核心是FIFO。priority_queue是“优先级依赖”的容器,适合需要动态获取最值的场景,核心是优先级排序。- 两者均不支持随机访问,只能通过特定接口(
front/top)访问特定元素,且插入/删除操作的时间复杂度不同(queue为 O(1),priority_queue为 O(log n))。
小根堆(最小值优先)
优先队列默认是大根堆,这里我们要用到 greater<T> ,并配合底层容器(例如vector)使用。
核心原理
original Template of priority_queue:
template <
class T, // 元素类型
class Container = vector<T>, // 底层容器(默认vector)
class Compare = less<T> // 比较器(默认less<T>,大根堆)
> class priority_queue;
less<T>``会让更大的元素优先级更高(大根堆的标志) 对应的小根堆就是greater
小根堆的声明
(T,vector<T>,greater<T>)
priority_queue<int,vector<int>,greater<int>> q;
在 C++ 中,priority_queue 默认是大根堆(最大值优先),若要实现小根堆(最小值优先),需显式指定比较器 greater<T>,并配合底层容器(通常为 vector)使用。以下是具体实现方法:
核心原理
priority_queue 的模板定义为:
template <
class T, // 元素类型
class Container = vector<T>, // 底层容器(默认vector)
class Compare = less<T> // 比较器(默认less<T>,大根堆)
> class priority_queue;
- 默认的
less<T>会让更大的元素优先级更高(大根堆)。 - 若将比较器改为
greater<T>,则更小的元素优先级更高,从而实现小根堆。
关键说明
-
greater<T>是 STL 提供的比较器(定义在<functional>头文件中,通常被其他头文件间接包含),其逻辑为:对于元素a和b,若a > b为true,则b的优先级更高(即更小的元素排在前面)。 -
必须显式指定底层容器(如
vector<int>),因为priority_queue的默认容器是vector,但当手动指定比较器时,容器参数不能省略。 -
小根堆的核心优势是高效获取和删除最小值,插入和删除操作的时间复杂度均为
O(log n),适合需要频繁处理最小值的场景(如 Dijkstra 算法、TOP-K 最小元素问题等)。
自定义类型的小根堆
对于自定义结构体/类,需配合 greater<T> 重载 > 运算符,或自定义比较器。示例如下:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct Student {
string name;
int score;
// 重载 > 运算符,配合greater<Student>使用
bool operator>(const Student& other) const {
return score > other.score; // 分数小的优先级更高(小根堆)
}
};
int main() {
// 自定义类型的小根堆
priority_queue<Student, vector<Student>, greater<Student>> min_heap;
min_heap.push({"Alice", 90});
min_heap.push({"Bob", 80});
min_heap.push({"Charlie", 85});
while (!min_heap.empty()) {
auto top = min_heap.top();
cout << top.name << "(" << top.score << "), ";
min_heap.pop();
}
// 输出:Bob(80), Charlie(85), Alice(90),(按分数从小到大)
return 0;
}
双端队列
在 C++ 中,std::deque(双端队列)是标准库 <deque> 提供的容器,支持在两端高效地插入和删除元素,同时也支持随机访问(类似数组)。它的底层实现通常是分段数组(多个连续内存块的集合),兼顾了动态数组的随机访问效率和链表的两端操作灵活性。
核心特性
- 双向操作高效:两端的插入(
push_front/push_back)和删除(pop_front/pop_back)操作时间复杂度为 O(1)。 - 随机访问:通过下标
[]或at()访问元素,时间复杂度 O(1)(优于std::list)。 - 动态扩容:无需预先指定大小,会自动分配内存,避免固定数组的容量限制。
- 迭代器支持:提供随机访问迭代器(
RandomAccessIterator),支持迭代器算术运算(如it + n)。
常用操作
以下是 std::deque 的核心成员函数(假设有 std::deque<int> dq):
| 操作 | 功能描述 | 时间复杂度 |
|---|---|---|
dq.push_back(x) |
在尾部插入元素 x |
O(1) |
dq.push_front(x) |
在头部插入元素 x |
O(1) |
dq.pop_back() |
删除尾部元素(不返回值) | O(1) |
dq.pop_front() |
删除头部元素(不返回值) | O(1) |
dq.front() |
返回头部元素的引用(不检查空容器) | O(1) |
dq.back() |
返回尾部元素的引用(不检查空容器) | O(1) |
dq[i] |
返回下标 i 处元素的引用(不检查越界) |
O(1) |
dq.at(i) |
返回下标 i 处元素的引用(越界抛异常) |
O(1) |
dq.empty() |
判断容器是否为空 | O(1) |
dq.size() |
返回元素个数 | O(1) |
dq.clear() |
清空所有元素 | O(n) |
dq.insert(pos, x) |
在迭代器 pos 处插入元素 x |
O(n) |
dq.erase(pos) |
删除迭代器 pos 处的元素 |
O(n) |
使用示例
#include <iostream>
#include <deque> // 需包含头文件
int main() {
std::deque<int> dq;
// 尾部插入
dq.push_back(10);
dq.push_back(20); // dq: [10, 20]
// 头部插入
dq.push_front(5); // dq: [5, 10, 20]
// 访问元素
std::cout << "头部元素: " << dq.front() << "\n"; // 5
std::cout << "尾部元素: " << dq.back() << "\n"; // 20
std::cout << "下标1的元素: " << dq[1] << "\n"; // 10
// 删除操作
dq.pop_front(); // 移除头部:dq: [10, 20]
dq.pop_back(); // 移除尾部:dq: [10]
// 遍历
for (int num : dq) {
std::cout << num << " "; // 输出:10
}
return 0;
}
与其他容器的对比
| 容器 | 优势 | 劣势 |
|---|---|---|
std::deque |
两端操作O(1),支持随机访问 | 中间插入/删除O(n),内存碎片可能较多 |
std::vector |
尾部操作O(1),内存连续,缓存友好 | 头部/中间操作O(n),扩容可能导致拷贝 |
std::list |
任意位置插入/删除O(1) | 不支持随机访问,迭代器仅支持双向移动 |
典型应用场景
- 实现队列(
push_back+pop_front)或栈(push_back+pop_back)。 - 滑动窗口问题(需频繁从两端删除元素,中间访问元素)。
- 需要高效随机访问且两端操作频繁的场景。
std::deque 是平衡了灵活性和效率的容器,在多数双向操作场景中是首选。

浙公网安备 33010602011771号