一、priority_queue简介
priority_queue
(优先队列)是C++标准模板库(STL)中用于管理元素优先级的容器适配器。与普通队列(FIFO)不同,优先队列每次从队列中取出的元素总是优先级最高的元素(默认是最大值),这使得它在需要频繁获取极值的场景中非常有用,例如堆排序、Dijkstra算法、Prim算法等。
二、核心特性
1. 底层实现
- 数据结构:优先队列的底层通过堆(Heap)实现,通常是用数组存储的二叉堆(大顶堆或小顶堆)。
- 堆的性质:
- 大顶堆:父节点的值大于等于子节点的值,根节点为最大值。
- 小顶堆:父节点的值小于等于子节点的值,根节点为最小值。
2. 访问方式
- 只能通过
top()
访问堆顶元素(优先级最高的元素)。 - 无法直接访问队列中间的元素,必须通过
pop()
移除堆顶元素后,新的堆顶元素才会暴露出来。
3. 常用操作
操作 | 描述 |
| 判断队列是否为空,空则返回 |
| 返回队列中元素的个数。 |
| 返回堆顶元素(优先级最高的元素),若队列为空,行为未定义。 |
| 向队列中插入新元素,并调整堆结构以维持堆的性质。 |
| 移除堆顶元素,并调整堆结构。注意:该函数不返回元素值,需配合 |
三、基础用法示例
1. 基本使用(大顶堆,默认情况)
#include <iostream>
#include <queue>
using namespace std;
int main() {
priority_queue<int> pq; // 大顶堆,默认比较方式
// 插入元素
pq.push(30);
pq.push(10);
pq.push(20);
cout << "堆顶元素(最大值):" << pq.top() << endl; // 输出30
// 弹出堆顶元素
pq.pop();
cout << "新堆顶元素:" << pq.top() << endl; // 输出20
return 0;
}
2. 自定义小顶堆
通过修改比较函数,将默认的大顶堆改为小顶堆。
#include <queue>
// 方式1:使用greater<int>(内置类型适用)
priority_queue<int, vector<int>, greater<int>> min_heap;
// 方式2:自定义比较类(可用于自定义类型)
struct Compare {
bool operator()(int a, int b) {
return a > b; // 小顶堆:a的优先级低于b时,a排在后面
}
};
priority_queue<int, vector<int>, Compare> min_heap;
3. 自定义类型的优先队列
#include <string>
#include <queue>
struct Student {
string name;
int score;
Student(string n, int s) : name(n), score(s) {}
};
// 按分数从高到低排序(大顶堆)
struct StudentCompare {
bool operator()(const Student& s1, const Student& s2) {
return s1.score < s2.score; // 注意:priority_queue使用的是“大于”来构建大顶堆
}
};
int main() {
priority_queue<Student, vector<Student>, StudentCompare> pq;
pq.push(Student("Alice", 90));
pq.push(Student("Bob", 85));
pq.push(Student("Charlie", 95));
while (!pq.empty()) {
Student s = pq.top();
cout << s.name << ": " << s.score << endl; // 输出顺序:Charlie, Alice, Bob
pq.pop();
}
return 0;
}
四、原理剖析:堆的实现与调整
1. 堆的存储方式
二叉堆通常用数组存储,下标从0或1开始(STL中从0开始)。对于下标为i
的元素:
- 左子节点下标:
2*i + 1
- 右子节点下标:
2*i + 2
- 父节点下标:
(i - 1) / 2
2. 关键操作
- 插入(push):
- 将元素添加到数组末尾。
- 执行向上调整( percolate up):比较当前元素与父节点,若优先级更高则交换,直到堆性质恢复。
- 删除堆顶(pop):
- 将堆顶元素与最后一个元素交换,删除最后一个元素。
- 执行向下调整(percolate down):比较当前元素与子节点,若优先级低于子节点则交换,直到堆性质恢复。
五、应用场景
1. 堆排序
通过优先队列实现升序排序(利用大顶堆):
vector<int> heapSort(vector<int>& nums) {
priority_queue<int> pq(nums.begin(), nums.end());
vector<int> result;
while (!pq.empty()) {
result.push_back(pq.top());
pq.pop();
}
reverse(result.begin(), result.end()); // 转为升序
return result;
}
2. 寻找前K大元素
使用小顶堆维护一个大小为K的堆,堆顶为第K大元素:
vector<int> findTopK(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> min_heap;
for (int num : nums) {
if (min_heap.size() < k) {
min_heap.push(num);
} else if (num > min_heap.top()) {
min_heap.pop();
min_heap.push(num);
}
}
vector<int> result;
while (!min_heap.empty()) {
result.push_back(min_heap.top());
min_heap.pop();
}
return result;
}
3. 任务调度(贪心算法)
例如,在操作系统中调度具有优先级的任务,每次选择优先级最高的任务执行。
六、注意事项
- 访问空队列:调用
top()
或pop()
对空队列操作会导致未定义行为,需先用empty()
判断。 - 底层容器:
priority_queue
默认使用vector
作为底层容器,也可指定deque
,但必须满足随机访问和push_back()
/pop_back()
操作。 - 稳定性:优先队列不保证相同优先级元素的相对顺序,属于不稳定的数据结构。
- 迭代器支持:
priority_queue
不提供迭代器,无法直接遍历队列中的元素。
七、与其他容器的对比
容器 | 特点 | 适用场景 |
| 基于堆,每次取极值,不支持遍历。 | 极值查找、贪心算法 |
| 动态数组,支持随机访问和迭代器。 | 顺序存储、频繁访问中间元素 |
| 基于红黑树,自动排序,支持迭代器和有序遍历。 | 有序集合、去重 |
八、总结
priority_queue
是C++中处理优先级问题的高效工具,其核心在于利用堆结构实现快速的极值获取。通过自定义比较函数,它可以灵活适应各种优先级规则。在使用时,需注意其底层实现的特性(如不支持遍历、默认大顶堆等),并结合具体场景选择合适的容器。熟练掌握优先队列,能有效提升算法效率,尤其是在贪心、图论等问题中。