利用堆结构高效解决TopK问题
创作时间:
作者:
@小白创作中心
利用堆结构高效解决TopK问题
引用
CSDN
1.
https://blog.csdn.net/2302_78391795/article/details/139692179
在处理大规模数据时,如何快速找到前K个最大或最小的元素是一个常见的问题。本文将详细介绍如何使用堆结构高效解决TopK问题,包括算法思想、代码实现和性能分析等多个方面。
一、引言
TopK问题是指在给定的一组数据或数据流中,找出最大的K个元素或最小的K个元素。堆结构是一种特殊的树形数据结构,每个父节点的值都大于(或小于)其子节点。这种性质使得堆在解决某些问题时非常高效。特别是在处理大规模数据时,堆结构能够保持数据的有序性,同时插入和删除操作的时间复杂度较低,因此是解决TopK问题的理想选择。
二、堆的基本概念
堆是一种特殊的树形数据结构,每个父节点的值都大于(或小于)其子节点。这种性质使得堆在解决某些问题时非常高效。堆可以分为大顶堆和小顶堆两种类型:
- 大顶堆:每个父节点的值都大于或等于其子节点的值。
- 小顶堆:每个父节点的值都小于或等于其子节点的值。
三、使用堆解决TopK问题
算法思想概述
如果数据较少,可以模仿堆排序(不需要进行完整的堆排序):
- 对数组建堆
- 堆顶数据与堆尾数据交换
- 重新调整,循环k次
- 数组的最后k个数就是要求得前k个最大(最小)的数
如果数据较大,没有办法将全部数据写入数组,就只能采用我们今天要介绍的算法了:
下面是重点,敲黑板!
分三步走:
- 构建初始堆:从数据集中选择前K个元素并构建初始堆,求最大的k个元素建小堆,求最小的k个元素建大堆
- 处理数据流并维护堆:对于后续的数据,如果其大于(或小于)堆顶元素,则替换堆顶元素并重新调整堆;否则忽略该数据。 ——堆顶的数据相当于是一个准入门槛,是堆中的最小值。以求最大的k个元素为例,后续遍历的元素只有大于堆顶,才有机会入堆,并且因为堆顶是堆的最小值,不存在较大的数据挡在堆顶的情况
- 提取TopK元素:堆中的元素即为TopK元素,可以直接输出或进行后续处理。
四、算法实现(C语言)
这里以实现求前k各最大元素为例
之前已经写好的向下调整建小堆算法和交换函数:
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
void Adjustdown(HPDataType* a, int parent, int n)
{
assert(a);
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] < a[child])
child++;
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
造数据的过程以及TOPK问题的解决函数:
#if 1
//TOPK问题
void CreateNDate()
{
int n = 1000;
srand(time(NULL));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = rand() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
fin = NULL;
}
void PrintTopK(int k)
{
int* arr = (int*)malloc(sizeof(int) * k);
const char* file = "data.txt";
FILE* fin = fopen(file, "r");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fin, "%d", &arr[i]);
}
for (int i = (k - 2) / 2; i >= 0; i--)
{
Adjustdown(arr, i, k);
}
int x = 0;
while (fscanf(fin, "%d", &x) != EOF)
{
if (x > arr[0])
{
arr[0] = x;
Adjustdown(arr, 0, k);
}
}
printf("前k个元素:");
for (int i = 0; i < k; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
#endif
int main()
{
//test1();
//CreateNDate();
PrintTopK(10);
return 0;
}
五、性能分析
- 时间复杂度:
- 当处理大数据集时,使用堆来解决TOPK问题可以显著提高性能。具体而言,如果我们使用最小堆来找出前K个最大的元素,或者最大堆来找出前K个最小的元素,时间复杂度可以大致控制在O(NlogK)内,其中N是数据集的大小。这是因为我们需要遍历整个数据集(O(N)),并且在每次插入或删除堆顶元素时,堆都需要进行调整(O(logK))。
- 相比之下,如果直接使用排序算法(如快速排序或归并排序),其时间复杂度通常为O(NlogN),这在N远大于K时,性能会显著下降。
- 空间复杂度:
- 使用堆解决TOPK问题只需要维护一个大小为K的堆,因此空间复杂度为O(K)。这意味着无论数据集的大小如何,我们只需要存储K个元素,这在处理大规模数据集时非常有效。
- 算法效率:
- 堆排序是一种原地排序算法(in-place sorting),即只需要使用O(1)的额外空间来进行排序。但是,在使用堆解决TOPK问题时,我们并不直接进行排序,而是利用堆的特性(最大堆或最小堆)来快速找出前K个最大或最小的元素。这种策略在处理TOPK问题时更加高效,因为我们只需要关心前K个元素,而不需要对整个数据集进行排序。
- 稳定性:
- 堆排序是一种不稳定的排序算法,因为在调整堆的过程中可能会改变相等元素的相对顺序。但是,在解决TOPK问题时,我们并不关心元素的相对顺序,只关心它们的大小关系。因此,堆的这种不稳定性对于解决TOPK问题并没有太大影响。
- 应用场景:
- 使用堆解决TOPK问题在多个领域都有广泛的应用,如搜索引擎、推荐系统、数据分析和数据挖掘等。在这些场景中,我们经常需要从大量数据中快速找出前K个最重要或最高排名的元素,堆排序的优势得以充分体现。
热门推荐
《鸣潮》2.0今汐配队推荐
拒绝与被拒绝,我们的担忧是否过了头?
银行的现金管理类理财产品有哪些?
最强“免疫力开关”竟是睡眠!这9类人睡好可治病
睡眠时大脑做“记忆巩固”睡不足够影响这两方面能力!4个方法确保孩子充足睡眠
孩子突然发烧怎么办?医生解析原因及应对方法
马克思与他的犹太人身份:自我认同的探索
文献解读丨基于铁基催化剂的CO₂高效转化制备烯烃:Na,Mn催化助剂协同效应研究
正月十五元宵节,无论多忙,牢记“1要躲,3不吃,忌2样”
滤芯种类大全:从PP纤维到聚四氟乙烯滤芯的特点与应用
股骨头坏死患者什么时候需要换股骨头?
C++中的std::move函数详解:作用、使用场景及注意事项
酒店水单完全指南:作用、获取方法及注意事项
支票跳票处理全攻略:如何挽回损失?律师建议这样做!
油气技术为增储上产注入动能
胸肌达人必备!三个不可不知的锻炼动作
山羊屠宰去毛全攻略:四种实用方法详解
股骨头坏死的五大病因及预防措施
拓维信息:预计2024年亏损1亿元-1.5亿元
谭元元:从旧金山到苏州,以芭蕾艺术点亮中国派
谭元元担任艺术总监,舞剧《白蛇》历经“蜕变”蛇年焕新归来
大五人格测试结果以及分析
如何在服务器上进行容量预测
【探秘中华巴洛克・修旧如旧】绣花功夫修复百年老砖墙,细微之处见真章
吴昌硕画菊艺术特色简析
如何去除室内甲醛
高速电机的六大关键技术
绩效工资的法律依据有哪些规定
扣发的绩效可以申请劳动仲裁么
骆宾王写下大骂武则天的檄文,为何能得到武则天的夸赞?