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

[数据结构——lesson10.2堆的应用以及TopK问题]

目录

前言

学习目标

堆排序

TopK问题:

解法一:建立N个数的堆

解法二:建立K个数的堆(最优解)

完整代码

结束语


前言

上节内容我们详细讲解了堆[数据结构——lesson10.堆及堆的调整算法],接下来我们来讲解堆的一个经典应用——TopK问题

学习目标

  • 堆排序
  • 掌握堆的应用理解TopK问题

堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
  • 升序:建大堆
  • 降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。


TopK问题:

Top-K 问题是一类常见的算法和数据处理问题,指从包含 N 个元素的大量数据集合中找到前 K 个最大或最小的元素,通常 N 远大于 K。

Top-k问题在生活中是非常的常见,比如游戏中某个大区某个英雄熟练度最高的前10个玩家的排名,我们就要根据每个玩家对该英雄的熟练度进行排序,可能有200万个玩家,但我只想选出前10个,要对所有人去排个序吗?显然没这个必要。

再比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
 

  • 问题特点:数据量 N 极大时,若直接对所有数据排序(如快排,时间复杂度为 O (n log n)),不仅耗时久,还可能需将所有数据加载到内存,空间成本极高。而 K 通常很小,只需关注 “最大或最小的前 K 个”,无需对所有数据排序,因此需要更高效的算法来解决。

解法一:建立N个数的堆

建一个 N 个数的堆(C++中可用优先级队列priority_queue),不断的选数,选出前 k 个。

时间复杂度:建N个数的堆为O(N),获取堆顶元素 (也即是最值) 并删除掉堆顶元素为O(log2N),上述操作重复 k 次,所以时间复杂度为O(N+k*log2N)

【思考】

但是这样也会存在上述所讲的可能需将所有数据加载到内存,空间成本极高的问题,能否再优化一下呢?


解法二:建立K个数的堆(最优解)

解决思路堆排序

  • 若要找前 K 个最大的元素,则建立小顶堆
  • 若要找前 K 个最小的元素,则建立大顶堆
  • 首先用数据集合中前 K 个元素来建堆,然后将剩余的 N-K 个元素依次与堆顶元素比较;
  • 大于(针对小顶堆)小于(针对大顶堆)堆顶元素,则替换堆顶元素并重新调整堆;
  • 遍历完剩余元素后,堆中的 K 个元素就是所求的前 K 个最大或最小的元素。

时间复杂度:

▶ 建 k 个元素的堆为O(K);
▶ 遍历剩余的 N-K 个元素的时间代价为O(N-K),假设运气很差,每次遍历都入堆调整;
▶ 入堆调整:删除堆顶元素和插入元素都为O(log2K);
▶ 所以时间复杂度为O(k + (N-K)log2K)。当 N 远大于 K 时,为O(N*log2K),这种解法更优。

 

假如要找出最大的前 10 个数:

建立 10 个元素的小堆,数据集合中前 10 个元素依次放入小堆,此时的堆顶元素是堆中最小的元素,也是堆里面第 10 个最小的元素,
▶  然后把数据集合中剩下的元素与堆顶比较,若大于堆顶则去掉堆顶,再将其插入,
▶  这样一来,堆里面存放的就是数据集合中的前 10 个最大元素,
此时小堆的堆顶元素也就是堆中的第 10 个最大的元素

 

思考:为什么找出最大的前10个数,不能建大堆呢?

  • 找出最大的前 10 个数不能建大堆,原因在于大堆的特性会导致只能找到最大的数,而无法找到其余较大的数。
  • 大堆的性质是堆顶元素为堆中最大的元素。当使用 10 个元素建大堆时,堆顶就是这 10 个元素中最大的,若数据集合中还有其他更大的数,由于它们都小于当前堆顶元素,根据大堆的插入规则,这些数无法进入堆中。所以最终只能得到最大的那个数,无法找出前 10 个最大的数。
  • 相反,若建立小堆,堆顶是堆中最小的元素,当有比堆顶大的元素出现时,就可以替换堆顶元素,并通过调整堆结构使小堆性质得以维持,这样就能保证较大的数逐渐进入堆中,最终堆中的 10 个元素就是数据集合中前 10 个最大的数。

完整代码

以从1w个数里找出最大的前10个数为例:

#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<stdio.h>
#include<time.h>// 大堆调整
void max_heapify(int* arr, int i, int size)
{int largest = i;int left = 2 * i + 1;int right = 2 * i + 2;if (left < size && arr[left] > arr[largest])largest = left;if (right < size && arr[right] > arr[largest])largest = right;if (largest != i){int temp = arr[i];arr[i] = arr[largest];arr[largest] = temp;max_heapify(arr, largest, size);}
}// 构建大根堆
void build_max_heap(int* arr, int size)
{for (int i = size / 2 - 1; i >= 0; i--)max_heapify(arr, i, size);
}// 堆排序
void heap_sort(int* arr, int size)
{build_max_heap(arr, size);for (int i = size - 1; i > 0; i--) {int temp = arr[0];arr[0] = arr[i];arr[i] = temp;max_heapify(arr, 0, i);}
}// 获取前k个最小元素
void get_topk_smallest(int* arr, int n, int k, int* result)
{if (k > n) k = n;int* heap = (int*)malloc(k * sizeof(int));if (heap == NULL) {printf("内存分配失败\n");return;}// 取前k个元素构建大堆for (int i = 0; i < k; i++)heap[i] = arr[i];build_max_heap(heap, k);// 遍历剩余元素for (int i = k; i < n; i++){if (arr[i] < heap[0]){heap[0] = arr[i];max_heapify(heap, 0, k);}}// 排序结果并输出heap_sort(heap, k);for (int i = 0; i < k; i++)result[i] = heap[i];free(heap);
}// 生成随机数组
void generate_random_array(int* arr, int size, int min, int max)
{srand(time(NULL));for (int i = 0; i < size; i++){arr[i] = min + rand() % (max - min + 1);}
}// 打印数组
void print_array(int* arr, int size)
{for (int i = 0; i < size; i++){printf("%d ", arr[i]);if ((i + 1) % 10 == 0)printf("\n");}printf("\n");
}// 验证结果正确性(通过全排序对比)
void verify_result(int* arr, int n, int k, int* topk)
{// 创建数组副本并排序int* copy = (int*)malloc(n * sizeof(int));for (int i = 0; i < n; i++)copy[i] = arr[i];heap_sort(copy, n);  // 注意:这里堆排序是升序printf("\n验证结果(前10个最小元素):\n");printf("算法结果:");for (int i = 0; i < k; i++)printf("%d ", topk[i]);printf("\n正确结果:");for (int i = 0; i < k; i++)printf("%d ", copy[i]);printf("\n");// 检查是否一致int correct = 1;for (int i = 0; i < k; i++){if (topk[i] != copy[i]){correct = 0;break;}}printf("验证结果:%s\n", correct ? "正确" : "错误");free(copy);
}int main()
{const int N = 10000;  // 数据总量const int K = 10;     // 要找的最小元素个数int* arr = (int*)malloc(N * sizeof(int));int* topk = (int*)malloc(K * sizeof(int));// 生成10000个1到100000之间的随机数generate_random_array(arr, N, 1, 100000);printf("已生成10000个随机数\n");// 计算前10个最小元素clock_t start = clock();get_topk_smallest(arr, N, K, topk);clock_t end = clock();// 输出结果printf("\n最小的前10个数(升序排列):\n");print_array(topk, K);// 输出耗时double time_spent = (double)(end - start) / CLOCKS_PER_SEC;printf("计算耗时:%.6f秒\n", time_spent);// 验证结果verify_result(arr, N, K, topk);// 释放内存free(arr);free(topk);return 0;
}

运行结果:

结束语

经过上节堆的学习,这一节我们对于堆的Top K问题的学习与理解相对会轻松很多。

感谢您的三连支持!!!


文章转载自:

http://nNz9KaZu.wyLpy.cn
http://qnjxFB1M.wyLpy.cn
http://cd03putz.wyLpy.cn
http://gMvkA9tA.wyLpy.cn
http://pgsXu4D5.wyLpy.cn
http://af185WKm.wyLpy.cn
http://DQZ4UVwp.wyLpy.cn
http://NSjO1Nqn.wyLpy.cn
http://wjsnHpHz.wyLpy.cn
http://KAIryFei.wyLpy.cn
http://th4Tq9Br.wyLpy.cn
http://p1Ro0xL8.wyLpy.cn
http://CQN1x2VK.wyLpy.cn
http://AsljjTLx.wyLpy.cn
http://45VoBLQH.wyLpy.cn
http://Y9bcwFSz.wyLpy.cn
http://jJzBfhUm.wyLpy.cn
http://fxWphHj0.wyLpy.cn
http://N7QYyL1O.wyLpy.cn
http://HXDukbZT.wyLpy.cn
http://20QFuqLl.wyLpy.cn
http://yi2Qv5ea.wyLpy.cn
http://BrzVnLkr.wyLpy.cn
http://ZYxOk8Lm.wyLpy.cn
http://wbSL2ylK.wyLpy.cn
http://9lw0sSWl.wyLpy.cn
http://SOpIPiUG.wyLpy.cn
http://1iSfxpCD.wyLpy.cn
http://NmSuP9an.wyLpy.cn
http://tAxOy4MH.wyLpy.cn
http://www.dtcms.com/a/383521.html

相关文章:

  • 可可图片编辑 HarmonyOS(6)水印效果
  • 机器学习(四):支持向量机
  • 给定一个有序的正数数组arr和一个正数range,如果可以自由选择arr中的数字,想累加得 到 1~range 范围上所有的数,返回arr最少还缺几个数。
  • 《C++ 容器适配器:stack、queue 与 priority_queue 的设计》
  • Java 黑马程序员学习笔记(进阶篇8)
  • 无需标注的视觉模型 dinov3 自监督学习ssl
  • 多语言编码Agent解决方案(2)-后端服务实现
  • STM32F103C8T6通过SPI协议驱动74HC595数码管完全指南:从硬件原理到级联实现
  • 【系列文章】Linux中的并发与竞争[05]-互斥量
  • 海岛奇兵声纳活动的数学解答
  • 大模型入门实践指南
  • CSS 编码规范
  • Redis框架详解
  • Redis----缓存策略和注意事项
  • Redis的大key问题
  • 微服务学习笔记25版
  • 地址映射表
  • AI Agent 软件工程关键技术综述
  • 命令行工具篇 | grep, findstr
  • 6【鸿蒙/OpenHarmony/NDK】多线程调用 JS 总崩溃?用 napi_create_threadsafe_function 搞定线程安全交互
  • OpenTenBase分布式HTAP实战:从Oracle迁移到云原生数据库的完整指南
  • LabVIEW信号监测与分析
  • 【大模型算法工程师面试题】大模型领域新兴的主流库有哪些?
  • Java队列(从内容结构到经典练习一步到位)
  • Cherno OpenGL 教程
  • RT-DETRv2 中的坐标回归机制深度解析:为什么用 `sigmoid(inv_sigmoid(ref) + delta)` 而不是除以图像尺寸?
  • OpenCV入门教程
  • 深度学习-计算机视觉-目标检测三大算法-R-CNN、SSD、YOLO
  • 冰火两重天:AI重构下的IT就业图景
  • 从ENIAC到Linux:计算机技术与商业模式的协同演进——云原生重塑闭源主机,eBPF+WebAssembly 双引擎的“Linux 内核即服务”实践