std::make_heap默认构建大顶堆,小顶堆需显式传std::greater();否则pop_heap等操作结果错误。大顶堆:std::make_heap(v.begin(), v.end());小顶堆:std::make_heap(v.begin(), v.end(), std::greater())。

怎么用 std::make_heap 构建大顶堆和小顶堆
默认就是大顶堆,小顶堆必须显式传比较器,否则所有 pop_heap、push_heap 行为都会出错——比如你以为在维护最小值,结果弹出来的是最大值。
常见错误现象:std::make_heap(v.begin(), v.end()) 后调用 std::pop_heap 取到的不是最小值;或者手动写循环建堆时根节点值不符合预期。
- 大顶堆(默认):
std::make_heap(v.begin(), v.end()) - 小顶堆(必须):
std::make_heap(v.begin(), v.end(), std::greater<int>())</int> - 自定义类型必须提供可比性,比如
struct Node { int val; }; bool operator,否则编译失败 - 注意:
std::greater<t></t>要包含<functional>,漏掉会报error: 'greater' was not declared in this scope
为什么不能直接对 vector 排序完再调 make_heap
堆不是排序数组,它是满足偏序关系的完全二叉树结构。如果先 std::sort 再 make_heap,相当于拿一个有序序列强行“重排成堆”,不仅多余,还可能掩盖逻辑错误——比如你本意是动态维护 top-k,却误用了全量排序。
使用场景:需要频繁取极值 + 增删元素(如优先队列底层、滑动窗口最大值),而不是一次性求全局最值。
立即学习“C++免费学习笔记(深入)”;
-
make_heap时间复杂度是 O(n),比 n 次push_heap的 O(n log n) 更优,适合批量建堆 - 但建好之后每次插入仍要
push_heap(O(log n)),不能靠push_back+ 再次make_heap,那样是 O(n) 每次,性能崩盘 - vector 尾部插入后必须立刻
push_heap,否则堆结构失效;同理,pop_heap后要pop_back才真正删除
push_heap 和 pop_heap 的操作顺序不能颠倒
这两个函数只调整结构,不改变容器大小。很多人卡在这一步:调了 pop_heap 发现最后元素没变,以为失败了——其实它只是把堆顶换到了末尾,你还得手动 v.pop_back()。
错误写法:std::pop_heap(v.begin(), v.end()); auto x = v.back(); —— 此时 v.back() 是原堆顶,但 v.size() 没变,下次 pop_heap 还会处理这个残留值。
- 正确弹出最小值(小顶堆):
std::pop_heap(v.begin(), v.end(), std::greater<int>()); auto min_val = v.back(); v.pop_back();</int> - 正确插入新值:
v.push_back(x); std::push_heap(v.begin(), v.end(), std::greater<int>());</int> - 注意:所有 heap 算法都要求迭代器范围有效,
v.empty()时调pop_heap是未定义行为,务必判空
手写堆排序时,为什么下标从 0 开始比从 1 开始更容易出错
STL 容器索引从 0 开始,而经典教材讲堆常假设根在下标 1,子节点是 2i 和 2i+1。直接套用会导致越界或父子关系错位,比如 parent = (i-1)/2 写成 i/2,在 i=0 时除零或算错。
实际开发中几乎没人手写完整堆排序——除非面试或嵌入式无 STL 环境。用 STL 就别碰裸下标计算。
- STL 堆算法只依赖随机访问迭代器,跟具体下标无关,不用管内部怎么存
- 如果真要手写,统一用 0 起始公式:
parent = (i-1)/2,left = 2*i+1,right = 2*i+2,且必须检查left < size - 递归式
sift_down容易栈溢出,生产代码建议用 while 循环实现
pop_heap 后忘 pop_back,或者比较器漏传,问题往往不报错而是逻辑错。这些点不像语法错误能被编译器拦住,得靠对每一步数据状态的明确预期来守住。










