std::priority_queue默认使用基于std::vector的最大堆,通过std::make_heap等算法实现,支持o(1) top()但不支持修改堆顶或任意节点更新。

std::priority_queue 用的是什么堆
它底层就是 std::vector + std::make_heap/std::push_heap/std::pop_heap 那套基于数组的完全二叉树实现,具体是最大堆(默认),根最大,父节点 ≥ 子节点。不是手写指针树,也不用动态分配节点内存。
这意味着所有操作都依赖于下标计算:parent = (i-1)/2,left = 2*i+1,right = 2*i+2。没有额外指针开销,缓存友好,但不支持任意节点删除或减小键值。
- 构造时如果传入已有容器,会调用
std::make_heap—— 时间复杂度O(n),不是逐个push的O(n log n) - 每次
push先尾插再上浮(push_heap),pop先交换首尾再下沉(pop_heap),都是O(log n) - 不提供
find、decrease_key或迭代器修改接口 —— 这些不是堆的原始语义,STL 也没补
为什么 top() 是 O(1) 但不能修改堆顶
top() 只是返回底层容器的 front(),也就是数组第一个元素,当然 O(1)。但它返回的是 const_reference,你没法通过它改值 —— 改了就破坏堆序,而容器本身没能力自动修复。
常见误操作:q.top() = new_val; 会编译失败;强行用 const_cast 强转后修改,后续 push/pop 行为未定义。
立即学习“C++免费学习笔记(深入)”;
- 想更新某个元素?只能把旧值标记为无效,插入新值,靠业务逻辑跳过已失效项(lazy deletion)
- 需要频繁更新优先级?
std::priority_queue不适合,考虑std::set或带索引的堆(如 Boost.Heap) - 底层容器可换,比如用
std::deque替代std::vector,但通常没收益 ——deque随机访问常数更大,且make_heap等算法要求随机访问迭代器,虽支持但更慢
compare 参数怎么影响性能和行为
比较器决定堆序方向和稳定性。默认是 std::less<t></t> → 最大堆;换成 std::greater<t></t> 就变成最小堆。它参与每一次上浮/下沉中的比较,所以别写重载 operator 时做耗时操作。
注意:比较器对象在构造时拷贝进队列,不是全局单例。如果你用的是捕获 lambda,必须是可拷贝的(C++17 起),且捕获内容不能含指针或状态依赖外部生命周期。
- 自定义类型必须提供满足严格弱序(strict weak ordering)的比较,否则
push/pop行为未定义 —— 常见坑是用了或漏处理相等情况 - 如果比较逻辑涉及虚函数调用或系统调用(如文件检查),性能会断崖下跌,堆操作本身很快,瓶颈全在比较器里
- 不要依赖比较器抛异常 —— STL 堆算法不保证异常安全,一旦抛出,容器可能处于中间状态
size() 和 empty() 的开销真的只是 O(1) 吗
是的,因为它们直接转发到底层容器的 size() 和 empty(),而 std::vector 的这两个操作确实是 O(1)。但要注意:这不是“堆结构”本身的大小,而是存储所有元素的容器大小 —— 即使你刚 pop 了 100 次,只要没 shrink,容量(capacity)可能还是原来那么大。
也就是说,size() 准确反映当前有效元素个数,但内存占用不一定随 size() 缩减 —— 它不会自动 shrink_to_fit。
- 大量增删后想释放内存?得手动取底层容器:
q.c.clear(); q.c.shrink_to_fit();(c是受保护成员,需继承或友元访问) - 跨线程使用时,
size()和empty()本身不加锁,也不是原子操作 —— 如果其他线程正在push,读到的 size 可能瞬间过期 - 调试时打印
q.size()看起来正常,不代表堆序正确 —— size 不验证结构,只数元素个数










