一、基础认知
1.核心特征
- 容器适配器:默认vector为封装的容器
- 堆结构:默认实现大顶堆(最大结构位于堆顶)
- 自动排序:插入、删除自动维护堆结构
- 访问限制:只能访问堆顶元素
2.堆类型对比
类型 | 声明方式 | 仿函数 | 堆顶元素特征 |
大顶堆 | priority_queue<int> | less<T> | 最大值 |
小顶堆 | priority_queue<int,vector<int>,greater<int>> | greater<T> | 最小值 |
二、核心实现机制
1.仿函数(核心控制逻辑)
(1)本质与作用
- 本质:像函数一样使用的类对象
- 函数对象:重载了operator()的类实例
- 控制策略:通过比较结果决定元素位置
- 模板参数:Compare控制堆类型(默认less<T>)
(2)标准实现
// 大顶堆比较器(默认)
template<class T>
class Less {
public:
bool operator()(const T& x, const T& y) {
return x < y; // 父节点 < 子节点时交换
}
};
// 小顶堆比较器
template<class T>
class Greater {
public:
bool operator()(const T& x, const T& y) {
return x > y; // 父节点 > 子节点时交换
}
};
(3)指针类型的特殊处理
// 错误情况:直接使用默认比较器
priority_queue<int*> pq; // 比较指针地址而非实际值
// 正确方案:自定义仿函数
struct PtrCompare {
bool operator()(const int* a, const int* b) {
return *a < *b; // 解引用比较实际值
}
};
priority_queue<int*, vector<int*>, PtrCompare> pq;
三、底层实现剖析
1.类模板结构
namespace lynn {
template<class T,class Container = vector<T>,class Compare = less<T>>
class priority_queue {
private:
Container _con; // 底层容器(默认vector)
Compare _comp; // 比较器对象
// 堆调整算法...
};
}
2.关键算法实现
(1)向下调整算法(堆化)
void AdjustDown(int parent) {
size_t child = parent * 2 + 1; // 初始左孩子
while (child < _con.size()) {
// 选择符合条件的子节点
if (child+1 < _con.size() && _comp(_con[child], _con[child+1]))
++child;
// 父子节点比较交换
if (_comp(_con[parent], _con[child])) {
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
} else {
break;
}
}
}
(2)向上调整算法(插入维护)
void AdjustUp(int child) {
int parent = (child-1)/2;
while (child > 0) {
if (_comp(_con[parent], _con[child])) {
swap(_con[child], _con[parent]);
child = parent;
parent = (child-1)/2;
} else {
break;
}
}
}
3.堆构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last) {
// 数据填充
while (first != last) {
_con.push_back(*first++);
}
// Floyd建堆算法(从最后一个非叶子节点开始)
for (int i = (_con.size()-2)/2; i >= 0; --i) {
AdjustDown(i); // O(N)时间复杂度
}
}
四、核心接口实现
1.元素操作
操作 | 实现原理 | 时间复杂度 |
push() | 尾部插入后向上调整 | O(log N) |
pop() | 首尾交换后删除,堆顶向下调整 | O(log N) |
top() | 直接返回首元素 | O(1) |
2.代码实现
void push(const T& x) {
_con.push_back(x);
AdjustUp(_con.size()-1); // 新元素上浮
}
void pop() {
swap(_con[0], _con.back());
_con.pop_back();
AdjustDown(0); // 新堆顶下沉
}
const T& top() const {
return _con[0];
}
五、关键问题解析
1.仿函数的工作时机
触发时机 | 比较方向 |
AdjustUp向上调整时 | 子节点与父节点 |
AdjustDown向下调整时 | 父节点与子节点 |
循环调用AdjustDown时 | 多层级比较 |
2.推荐容器
- vector:默认选择,连续内存访问
- deque:分段连续内存结构
六、易错点提醒
1.仿函数方向混淆
- 大顶堆使用less<T>:当父节点<子节点时交换。
- 小顶堆使用greater<T>:当父节点>子节点时交换。
2.指针类型陷阱
// 错误示例:直接比较指针地址
priority_queue<string*> pq;
pq.push(new string("apple"));
pq.push(new string("banana")); // 比较的是内存地址!
// 正确方案:自定义解引用比较器
struct StringPtrCompare {
bool operator()(const string* a, const string* b) {
return *a < *b; // 按字符串内容排序
}
};
3.无效堆状态