算法实战:最小k个数------大顶堆的优雅解法
- [Bilibili 同步视频](#Bilibili 同步视频)
- [一、问题溯源:何为最小 k 个数](#一、问题溯源:何为最小 k 个数)
- 二、核心推演:候选集与最值维护
- 三、原理深剖:大顶堆为何适配
- 四、思维三重境:堆的理解与应用
- [五、C++ 代码实现:极简封装与调用](#五、C++ 代码实现:极简封装与调用)
- 六、性能分析
- 七、总结
在算法刷题与工程实践中,从海量数据中筛选最小 k 个元素 是极为经典的高频考点,其核心本质是集合最值的动态维护。诸多初学者常陷入暴力排序的思维桎梏,却不知堆结构恰是破解此类问题的神兵利器。本文将以逻辑推演为径,以代码实现为舟,层层解析大顶堆求解最小 k 个数的底层原理,助你贯通数据结构与算法应用的思维壁垒。
Bilibili 同步视频
一、问题溯源:何为最小 k 个数
给定一组无序整型数组,需从中精准提取出数值最小的 k 个元素。此问题看似简单,却暗藏性能权衡:若采用全量排序后截取前 k 位,时间复杂度可达O(nlogn) ,在数据规模庞大时效率堪忧;而借助堆结构动态维护候选集,可将时间复杂度优化至O(nlogk),当 k 远小于 n 时,性能优势极为显著。
二、核心推演:候选集与最值维护
破解此题的关键,在于构建动态候选集合,其核心逻辑可凝练为:
-
定义候选集:存储最有可能成为最小 k 值的元素,容量固定为 k;
-
动态准入规则:新元素若小于候选集内的最大值,则替换候选集中的最大值;
-
结构选型:需实时获取集合最大值,大顶堆为最优解。
文本原理图:候选集替换逻辑
Plain
初始候选集(k=3):[5, 8, 10] → 堆顶最大值:10
新元素:6
6 < 10 → 剔除10,加入6
新候选集:[5, 6, 8] → 堆顶最大值:8
简言之,大顶堆始终保留 k 个最小元素,堆顶即为当前候选集的最大值,一旦有更小元素闯入,便将最大值淘汰,全程仅需维护堆的最值特性,无需关注全量数据。
三、原理深剖:大顶堆为何适配
堆是一棵完全二叉树 ,大顶堆的核心特性为:堆顶元素恒为整个堆的最大值 ,且插入、删除操作的时间复杂度仅为O(logk)。
求解最小 k 个数时,堆的容量严格限制为 k,遍历数组时仅做两步操作:
-
新元素入堆;
-
若堆大小超 k,弹出堆顶最大值。
遍历结束后,堆中剩余元素即为数组中最小的 k 个数。此过程完全贴合问题需求,无需排序全量数据,完美实现以小空间换高效率。
四、思维三重境:堆的理解与应用
诸多学习者学懂堆结构,却做题无从下手,根源在于未完成三种思维的转换:
-
树形思维:初识堆,以完全二叉树理解其结构形态,明晰父子节点的大小关系;
-
一维思维:实现堆时,以数组为载体存储节点,通过下标计算完成堆化操作;
-
问题思维 :解题时,剥离底层实现,仅聚焦动态维护集合最值的核心性质,直击问题本质。
做题时无需纠结堆的数组实现、树形结构,只需牢记:大顶堆快速取最大值,小顶堆快速取最小值,即可精准匹配问题场景。
五、C++ 代码实现:极简封装与调用
本文基于封装大顶堆的思路实现,逻辑清晰易理解,代码注释详尽,适配算法面试与刷题场景。
关键代码(C++)
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 功能:获取数组中最小的k个数
vector<int> getLeastNumbers(vector<int>& arr, int k) {
// 大顶堆:priority_queue默认实现大顶堆
priority_queue<int> maxHeap;
for (int num : arr) {
// 新元素入堆
maxHeap.push(num);
// 堆大小超过k,弹出最大值
if (maxHeap.size() > k) {
maxHeap.pop();
}
}
// 堆中剩余元素即为最小k个数
vector<int> res;
while (!maxHeap.empty()) {
res.push_back(maxHeap.top());
maxHeap.pop();
}
return res;
}
// 测试示例
int main() {
vector<int> arr = {4, 5, 1, 6, 2, 7, 3, 8};
int k = 4;
vector<int> ans = getLeastNumbers(arr, k);
cout << "最小的" << k << "个数为:";
for (int num : ans) {
cout << num << " ";
}
return 0;
}
代码解析
-
借助 C++ STL 优先队列
priority_queue快速实现大顶堆,无需手动封装堆结构; -
遍历数组元素,逐次入堆,严格控制堆大小不超过 k;
-
超出容量时弹出堆顶最大值,保证堆内始终为当前最小的 k 个元素;
-
最终将堆中元素存入容器,即为所求结果。
六、性能分析
-
时间复杂度:O (nlogk),n 为数组长度,k 为目标元素个数,每次堆操作耗时 O (logk);
-
空间复杂度:O (k),仅需维护容量为 k 的堆结构,无需额外开辟大量空间。
相较于暴力排序的 O (nlogn) 复杂度,此方法在大数据量下性能提升极为明显,是面试中的最优解。
七、总结
最小 k 个数问题,看似是数据筛选,实则是动态最值维护 的经典应用。以大顶堆为刃,破题之关键在于:不恋全量排序,只守候选极值。

望读者透过此题,掌握堆结构的解题思维:先析问题性质,再选适配结构,而非死记硬背解法。自此,海量数据筛选、TopK 问题等同类题型,皆可迎刃而解,于算法之路上稳步前行。