当前位置: 首页 > news >正文

【数据结构】用堆解决TOPK问题

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4 输出: [1,2,3,4]

比较替换堆顶的数时,不需要让堆顶与数组的每一个数再进行比较,比较数组减去k个数之后剩下的数就行。

1.堆排序方法

// 选出最小的K个数,要建一个大堆// 大堆的向下调整算法
void HeapDown(int* heap, int k, int parent) {// 1.根据父亲结点得到左孩子节点下标childint child = parent * 2 + 1;// 2.进入while循环进行向下调整(while的循环条件是child<k)while (child < k) {// 2.1比较左孩子和右孩子的值,如果右孩子更大,child++,注意要避免越界child+1<kif (child + 1 < k && heap[child] < heap[child + 1]) {child++;}// 2.2如果父亲结点的值小于孩子结点,进行交换,交换后将child结点的下标赋值给父亲结点,//    根据此时的父亲结点计算下一个child的下标if (heap[parent] < heap[child]) {int temp = heap[parent];heap[parent] = heap[child];heap[child] = temp;parent = child;child = parent * 2 + 1;}// 2.3如果父亲结点的值已经大于孩子结点,说明不需要再调整,break跳出循环elsebreak;}
}int* smallestK(int* arr, int arrSize, int k, int* returnSize) {// 0.首先处理两种特殊情况K<=0,K>=arrSzieif (k <= 0) {*returnSize = 0;return NULL;}if (k >= arrSize) {int* result = (int*)malloc(sizeof(int) * arrSize);memcpy(result, arr, sizeof(int) * arrSize);*returnSize = arrSize;return result;}// 1.首先开辟一个空间存我们新建的大堆数据int* heap = (int*)malloc(sizeof(int) * arrSize);// 2.将传入的数组的数据memcpy到我们新建的空间memcpy(heap, arr, sizeof(int) * arrSize);// 3.从下往上进行向下调整构建大堆for (int i = (arrSize - 1 - 1) / 2; i >= 0; i--) {HeapDown(heap, arrSize, i);}int end = arrSize - 1;while (end > 0) {int tem = heap[0];heap[0] = heap[end];heap[end] = tem;end--;HeapDown(heap, end, 0);}int* arrtemp = (int*)malloc(sizeof(int) * k);memcpy(arrtemp, heap, sizeof(int) * k);*returnSize = k;return arrtemp;
}
  1. 边界条件的严谨性
    代码开头对k的特殊情况(k<=0k>=数组长度)做了单独处理:

    • k<=0时,返回空数组并设置returnSize=0
    • k大于等于数组长度时,直接返回原数组。
      这种处理避免了后续无效的堆操作,也防止了数组访问越界,体现了对 “异常输入” 的考虑,是健壮性代码的常见做法。
  2. 堆操作的标准化实现

    • 向下调整算法(HeapDown):这是堆操作的核心,代码正确实现了大堆的向下调整逻辑:
      ① 先比较左右孩子,选择更大的子节点(保证大堆特性);
      ② 若父节点小于子节点,则交换两者,并继续向下调整;
      ③ 若父节点已大于子节点,说明堆已平衡,直接退出。
      其中对child+1 < k的越界检查(避免右孩子不存在时的访问错误),体现了细节处理的严谨性。

    • 堆的构建方式:从最后一个非叶子节点((arrSize-1-1)/2)开始,向上依次调用HeapDown,这是构建堆的标准高效方法(时间复杂度O(n)),比从根节点逐个插入(O(n log n))更优。

  3. 接口设计的规范性
    函数通过returnSize参数返回结果数组的长度,符合 C 语言中 “动态数组需明确长度” 的设计习惯(调用者可通过returnSize正确遍历结果),避免了因长度未知导致的越界访问。

最优解:

// 选出最小的K个数,要建一个大堆// 大堆的向下调整算法
void HeapDown(int* heap, int k, int parent) {// 1.根据父亲结点得到左孩子节点下标childint child = parent * 2 + 1;// 2.进入while循环进行向下调整(while的循环条件是child<k)while (child < k) {// 2.1比较左孩子和右孩子的值,如果右孩子更大,child++,注意要避免越界child+1<kif (child + 1 < k && heap[child] < heap[child + 1]) {child++;}// 2.2如果父亲结点的值小于孩子结点,进行交换,交换后将child结点的下标赋值给父亲结点,//    根据此时的父亲结点计算下一个child的下标if (heap[parent] < heap[child]) {int temp = heap[parent];heap[parent] = heap[child];heap[child] = temp;parent = child;child = parent * 2 + 1;}// 2.3如果父亲结点的值已经大于孩子结点,说明不需要再调整,break跳出循环elsebreak;}
}int* smallestK(int* arr, int arrSize, int k, int* returnSize) {// 0.首先处理两种特殊情况K<=0,K>=arrSzieif (k <= 0) {*returnSize = 0;return NULL;}if (k >= arrSize) {int* result = (int*)malloc(sizeof(int) * arrSize);memcpy(result, arr, sizeof(int) * arrSize);*returnSize = arrSize;return result;}// 1.首先开辟一个空间存我们新建的大堆数据int* heap = (int*)malloc(sizeof(int) * k);// 2.将传入的数组的数据memcpy到我们新建的空间memcpy(heap, arr, sizeof(int) * k);// 3.从下往上进行向下调整构建大堆for (int i = (k - 1 - 1) / 2; i >= 0; i--) {HeapDown(heap, k, i);}// 4.将新建大堆的堆顶值与传入数组剩下的arrSize-k个数据进行比较,如果堆顶的数更大,替换堆顶的数并进行向下调整//j从k开始即可for (int j = k; j < arrSize; j++) {if (heap[0] > arr[j]) {//直接替换即可heap[0]=arr[j];HeapDown(heap, k, 0);}}*returnSize = k;return heap;
}
  1. 大堆的精准应用
    选出 “最小的 K 个数” 时,使用 “大堆” 是非常巧妙的选择:

    • 大堆的堆顶始终是当前 K 个元素中的最大值,便于快速判断新元素是否有资格进入 “最小 K 个” 的集合(只需比较新元素与堆顶)。
    • 若新元素更小,则替换堆顶并调整,保证堆中始终是当前最小的 K 个元素。
      这种思路体现了 “用合适的数据结构解决特定问题” 的设计思想。
  2. 堆构建的高效实现

    • 构建堆时,从最后一个非叶子节点((k-1-1)/2)开始向上调用HeapDown,这是堆初始化的标准高效方法(时间复杂度O(k)),而非从根节点逐个插入(O(k log k))。
    • HeapDown函数的实现严谨:先比较左右孩子取较大值,再与父节点比较交换,确保大堆特性,且对右孩子的越界检查(child+1 < k)避免了数组访问错误。
  3. 内存使用的合理性

    • 堆空间直接分配为k大小(malloc(sizeof(int)*k)),精准匹配需求,不浪费内存。
    • 最终直接返回堆空间作为结果,避免了额外的内存拷贝(上一版中arrtemp的拷贝操作)。

http://www.dtcms.com/a/339058.html

相关文章:

  • 算法训练营day56 图论⑥ 108. 109.冗余连接系列
  • C++---为什么迭代器常用auto类型?
  • 强、软、弱、虚引用
  • 在 Qt C++ 中利用 OpenCV 实现视频处理技术详解
  • 尝试Claude Code的安装
  • 学习笔记分享——基于STM32的平衡车项目
  • Mac调试ios的safari浏览器打开的页面
  • 电子电气架构 --- 软件项目成本估算
  • 技术攻坚全链铸盾 锁定12月济南第26届食品农产品安全高峰论坛
  • 任务十二 我的页面及添加歌曲功能开发
  • Typescript入门-对象讲解
  • Python量化交易:结合爬虫与TA-Lib技术指标分析
  • Matplotlib数据可视化实战:Matplotlib子图布局与管理入门
  • Ansible 角色管理指南
  • Pandas数据处理与分析实战:Pandas数据处理与Matplotlib可视化入门
  • 0819 使用IP多路复用实现TCP并发服务器
  • Tomcat 的核心脚本catalina.sh 和 startup.sh的关系
  • 陪诊小程序系统开发:开启智慧就医新时代
  • CNN 在故障诊断中的应用:原理、案例与优势
  • BEV:隐式相机视角转换-----BEVFormer
  • 简单实现监听redis的Key过期事件
  • Shopee本土店账号安全运营:规避封禁风险的多维策略
  • 微服务-08.微服务拆分-拆分商品服务
  • 什么是强化学习
  • JMeter高级性能测试训练营 – 从入门到企业级实战
  • pytest高级用法之插件开发
  • Quartus Prime 18.1网盘资源下载与安装指南
  • 从线性回归到神经网络到自注意力机制 —— 激活函数与参数的演进
  • Berry Material React TypeScript 管理后台使用教程 v0.1.0
  • 手写C++ string类实现详解