用最小堆找Top-K大数,本质是维护容量为K的候选池,堆顶为池中最小值,可快速淘汰劣质元素;新数大于堆顶即入池,确保池内始终为当前最大的K个数;时间复杂度O(n log k),优于全排序的O(n log n)。

为什么用最小堆而不是最大堆
找 Top-K 大的数,本质是维护一个“容量为 K 的候选池”,池里始终存着当前见过的最大的 K 个数。最小堆的堆顶是最小值,正好能快速淘汰掉池中最小的那个——一旦新数比堆顶大,说明它更有资格进池,就把堆顶踢掉、插入新数。最大堆做不到这点:堆顶是最大值,没法快速判断“当前数是否值得留下”。
时间复杂度上,最小堆法是 O(n log k),而排序整个数组是 O(n log n),当 k (比如 100 万数据里找前 10 个)时优势明显。
std::priority_queue 默认不是最小堆,怎么改
std::priority_queue 默认是最大堆(基于 std::less<int>),要变成最小堆,必须显式指定比较器:std::greater<int>。漏掉这一步,代码逻辑就全反了——你会得到最小的 K 个数。
常见写法:
立即学习“C++免费学习笔记(深入)”;
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
注意三点:
- 第三个模板参数不能省,
std::greater<int>要带<int>,否则编译失败 - 第二个参数
std::vector<int>是容器类型,不能写成vector<int>(缺std::) - 如果数据是
long long或自定义类型,比较器和类型必须严格匹配,否则运行时可能堆顶异常或编译报错
建堆过程要边读边维护,别等全读完再建
海量数据通常无法一次性加载进内存(比如文件有 10GB),所以不能先 std::vector 全存再 make_heap。正确做法是流式处理:逐个读入数字,边读边更新堆。
具体策略:
- 前 K 个数直接入堆
- 从第 K+1 个数开始,只在
num > min_heap.top()时才执行pop()+push(num) - 最后堆里剩下的 K 个数就是 Top-K,但顺序是乱的(堆不保证整体有序),如需降序输出得额外排序或用栈暂存后倒序
错误示范:if (min_heap.size() < k) min_heap.push(num); else if (num > min_heap.top()) { min_heap.pop(); min_heap.push(num); } —— 这个逻辑是对的,但要注意 min_heap.size() 在空堆上调用是安全的,不过首次 push 前别误判 size。
重复元素和边界情况怎么处理
题目没说“去重”,所以重复数算独立个体。比如数据是 [5,5,5,4,3],K=3,结果应为 [5,5,5],不是 [5,4,3]。最小堆天然支持重复值,无需特殊处理。
但边界容易出错:
- K 为 0 或大于数据总量:提前检查,
if (k == 0) return {};,避免堆为空时调用top()导致段错误 - 数据全相同:堆会稳定维持 K 个相同值,没问题
- K 等于数据总量:堆最终装满,退化为堆排序前半步,但没必要,此时直接返回原数组更高效
真正难的是数据源不可随机访问(如网络流、传感器实时数据),这时还得考虑堆的线程安全和内存碎片——标准库堆不保证并发安全,多线程场景得加锁或换用无锁结构。


















