当前位置: 首页 > 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.lryc.cn/news/625883.html

相关文章:

  • 服务器数据恢复—硬盘坏道离线导致raid崩溃的StorNext文件系统数据恢复案例
  • 深度学习-167-MCP技术之工具函数的设计及注册到MCP服务器的两种方式
  • 应用控制技术、内容审计技术、AAA服务器技术
  • Commons-io
  • Syntax Error: Error: PostCSS received undefined instead of CSS string
  • CSS封装大屏自定义组件(标签线)
  • 2025年6月中国电子学会青少年软件编程(图形化)等级考试试卷(一级)答案 + 解析
  • LangChain —多模态 / 多源上下文管理
  • 云原生俱乐部-mysql知识点归纳(3)
  • 【论文阅读】SIMBA: single-cell embedding along with features(1)
  • 《Dual Prompt Personalized Federated Learning in Foundation Models》——论文阅读
  • 自然语言处理(NLP)技术的发展历史
  • 【QT入门到晋级】进程间通信(IPC)-socket(包含性能优化案例)
  • Python爬虫实战:研究ICP-Checker,构建ICP 备案信息自动查询系统
  • GIS在海洋大数据的应用
  • 数据结构:深入解析常见数据结构及其特性
  • 3 创建wordpress网站
  • 【实时Linux实战系列】实时大数据处理与分析
  • 【数据库】通过‌phpMyAdmin‌管理Mysql数据
  • 计算机毕设推荐:痴呆症预测可视化系统Hadoop+Spark+Vue技术栈详解
  • [Polly智能维护网络] 网络重试原理 | 弹性策略
  • 图像采集卡与工业相机:机器视觉“双剑合璧”的效能解析
  • CMake进阶: CMake Modules---简化CMake配置的利器
  • 小迪安全v2023学习笔记(六十六讲)—— Java安全SQL注入SSTISPELXXE
  • Webpack 5 配置完全指南:从入门到精通
  • 云手机矩阵:重构企业云办公架构的技术路径与实践落地
  • HarmonyOS 中的 泛型类和泛型接口
  • oc-mirror plugin v2 错误could not establish the destination for the release i
  • 力扣hot100:三数之和(排序 + 双指针法)(15)
  • 缓存-变更事件捕捉、更新策略、本地缓存和热key问题