下面是关于 C++ 中 std::priority_queue
的详细说明,包括初始化、用法和常见的应用场景。
什么是 priority_queue
?
priority_queue
(优先队列)是 C++ 标准库中的一个容器适配器。它和普通队列(queue
)最大的不同在于,元素的出队顺序不是根据它们入队的顺序(先进先出),而是根据它们的优先级。
默认情况下,priority_queue
是一个大顶堆(Max-Heap),意味着优先级最高的元素是值最大的元素。每次调用 top()
都会返回当前队列中最大的元素。
- 底层实现:通常使用**堆(Heap)**数据结构来实现,这使得它能以对数时间复杂度(O(log n))插入元素和删除最大(或最小)的元素。
- 头文件:使用前需要包含头文件
<queue>
。
#include <iostream>
#include <queue>
#include <vector>
#include <functional> // 包含 std::greater 和 std::less
1. priority_queue
的初始化
priority_queue
的模板定义如下:
template <class T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type>>
class priority_queue;
T
: 存储的元素类型。Container
: 用于存储元素的底层容器,默认为std::vector<T>
。必须是支持随机访问迭代器和front()
,push_back()
,pop_back()
等操作的序列容器,例如std::vector
和std::deque
。Compare
: 比较函数或函数对象,用于确定元素的优先级。默认为std::less<T>
,它会创建一个大顶堆。
1.1 默认初始化(大顶堆)
最常见的用法,创建一个存储 int
类型的大顶堆。每次 top()
都会返回最大的元素。
// 默认情况下,是 std::vector 作为容器,std::less 作为比较函数,即大顶堆
std::priority_queue<int> max_heap;max_heap.push(30);
max_heap.push(100);
max_heap.push(25);
max_heap.push(40);// 此刻队列顶部的元素是 100
std::cout << "Top element is: " << max_heap.top() << std::endl; // 输出 100
1.2 初始化为小顶堆
如果你想让 top()
总是返回最小的元素,你需要创建一个小顶堆(Min-Heap)。这需要显式提供所有模板参数:
T
: 元素类型。Container
: 底层容器,通常是std::vector<T>
。Compare
: 比较函数,使用std::greater<T>
。
// 使用 std::greater<int> 来创建小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;min_heap.push(30);
min_heap.push(100);
min_heap.push(25);
min_heap.push(40);// 此刻队列顶部的元素是 25
std::cout << "Top element is: " << min_heap.top() << std::endl; // 输出 25
1.3 存储自定义结构体
当存储自定义类型(如 struct
或 class
)时,你需要告诉优先队列如何比较它们。有两种主要方法:
方法一:重载 <
运算符(用于大顶堆)
struct Node {int id;int priority;// 重载 < 运算符,priority 越大,优先级越高bool operator<(const Node& other) const {return this->priority < other.priority;}
};std::priority_queue<Node> pq;
pq.push({1, 10});
pq.push({2, 5});
pq.push({3, 20});// top() 将返回 priority 最大的 Node,即 {3, 20}
std::cout << "Top node: id=" << pq.top().id << ", priority=" << pq.top().priority << std::endl;
方法二:提供自定义比较器(更灵活)
如果你不想或不能修改结构体定义,或者需要多种排序方式,可以提供一个自定义的比较函数对象(Functor)。
struct Node {int id;int priority;
};// 自定义比较器结构体
struct CompareNode {// 返回 true 表示 a 的优先级低于 bbool operator()(const Node& a, const Node& b) {// 创建小顶堆,priority 越小,优先级越高return a.priority > b.priority;}
};std::priority_queue<Node, std::vector<Node>, CompareNode> pq;
pq.push({1, 10});
pq.push({2, 5});
pq.push({3, 20});// top() 将返回 priority 最小的 Node,即 {2, 5}
std::cout << "Top node: id=" << pq.top().id << ", priority=" << pq.top().priority << std::endl;
使用 Lambda 表达式(C++11及以上)也可以实现,但定义比较器时需要 decltype
。
1.4 从已有容器初始化
你可以用一个已有的容器(如 vector
)中的元素来初始化优先队列。
std::vector<int> data = {30, 100, 25, 40};// 从 data 初始化一个大顶堆
std::priority_queue<int> max_heap_from_vec(data.begin(), data.end());std::cout << "Top element from vector init: " << max_heap_from_vec.top() << std::endl; // 输出 100// 初始化小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap_from_vec(data.begin(), data.end());
std::cout << "Top element from vector init (min-heap): " << min_heap_from_vec.top() << std::endl; // 输出 25
2. priority_queue
的常用操作
优先队列的操作非常简单直观:
push(element)
: 向队列中插入一个元素。时间复杂度 O(log n)。pop()
: 移除队首(优先级最高)的元素。时间复杂度 O(log n)。top()
: 返回队首(优先级最高)元素的常引用,不删除元素。时间复杂度 O(1)。empty()
: 检查队列是否为空。size()
: 返回队列中元素的数量。
示例代码:
std::priority_queue<int> pq;std::cout << "Is queue empty? " << (pq.empty() ? "Yes" : "No") << std::endl;pq.push(10);
pq.push(50);
pq.push(20);std::cout << "Queue size: " << pq.size() << std::endl;
std::cout << "Top element: " << pq.top() << std::endl; // 50pq.pop(); // 移除了 50std::cout << "Top element after pop: " << pq.top() << std::endl; // 20// 遍历并清空优先队列
std::cout << "Queue elements in priority order: ";
while (!pq.empty()) {std::cout << pq.top() << " ";pq.pop();
}
std::cout << std::endl;
// 输出: Queue elements in priority order: 20 10
注意:priority_queue
没有提供迭代器,不能像 vector
那样直接遍历。唯一的访问方式就是通过 top()
获取队首元素。
3. priority_queue
的应用场景
优先队列在算法和系统设计中非常有用,尤其适合处理那些需要动态维护“最大/最小”元素的问题。
3.1 Top-K 问题
这是最经典的应用。例如,从一个巨大的数据流中找出最大的 K 个元素。
思路:
- 维护一个大小为 K 的小顶堆。
- 遍历数据流,对于每个新元素:
- 如果堆的大小小于 K,直接将元素插入堆中。
- 如果堆的大小等于 K,将新元素与堆顶元素(当前 K 个元素中最小的那个)比较。
- 如果新元素大于堆顶元素,则弹出堆顶,并将新元素插入。
- 遍历结束后,堆中剩下的 K 个元素就是最大的 K 个元素。
为什么用小顶堆? 因为小顶堆的堆顶是“门槛”,只有比这个门槛大的元素才有资格进入这个“Top-K 圈子”,这样可以高效地淘汰掉不够大的元素。
3.2 堆排序(Heap Sort)
虽然 C++ 提供了 std::sort
,但优先队列可以很自然地实现堆排序。
- 将所有元素插入一个大顶堆。
- 依次从堆中取出堆顶元素,放入结果数组中。取出的顺序就是从大到小。
3.3 Dijkstra 算法
在图论中,Dijkstra 算法用于寻找图中单源最短路径。算法的核心是每次都从“待处理”的顶点中,选择一个距离源点最近的顶点进行扩展。优先队列(小顶堆)可以完美地胜任这个任务。
- 优先队列中存储
pair<int, int>
,即{距离, 顶点编号}
。 - 每次从队列中取出
top()
,就是当前距离源点最近的未访问顶点。
3.4 Huffman 编码
在数据压缩中,Huffman 编码算法使用优先队列来构建最优二叉树(Huffman 树)。
- 将每个字符及其频率作为一个节点放入一个优先队列(小顶堆)中,频率越小,优先级越高。
- 每次从队列中取出两个频率最小的节点,合并成一个新的父节点(频率为两者之和),再将新节点放回优先队列。
- 重复此过程,直到队列中只剩一个节点,即 Huffman 树的根。
3.5 任务调度系统
在操作系统或中间件中,任务调度器需要从多个待执行的任务中选择一个优先级最高的来执行。优先队列可以用来管理这些任务,top()
总是返回当前最紧急的任务。
希望这份详细的说明对你有帮助!