【数据结构】TopK问题
创始人
2024-04-21 15:53:56
0

TopK

在N个数中 找出最大或最小的前k个数,就是TopK算法。比如有一组数字,[1,2,3,4,5,6,7,8,9],这组数中最大的前 3(k)个数是 9,8,7。最小的 前3个数是 1,2,3。而TopK算法就是找出最大或者最小的前K个数。我们就用堆来实现。

之前讲解过如何写一个堆,博客链接 ,代码git链接。
今天我们就用这个堆,来实现TopK算法。

找到前k个最大的数的方法有很多,我们可以取堆顶,然后在pop掉堆顶,Pop K次,如果是大堆,那么就是前K个最大的数。如果是小堆,那么就是前K个最小的数。 但再此之前我们要先把 数据依次 Push到堆中,但这个方式有一个弊端,如果数据很多,有1亿个,是从文件中读取的话。那么要把1亿个数全都读到内存,但是内存的的空间是有限的,放不下如此多的数据,所以这个方法就不可行了,今天就给大家介绍一种更方便的方法。

算法思路

我们可以构建一个 有K个数的小堆,依次Push。
比如上面举的例子,1-9,找出最大的前3个数。
那么此时,K = 3, 找最大的前3个数,我们就构建小堆。
我们随机取3个数,2,6,9,放入小堆中。
在这里插入图片描述
随后我们拿剩下 N - K个数与堆顶进行比较,如果比堆顶大,那么就用这个数替换堆顶,随后进行向下调整。

第一次比较
在这里插入图片描述

第二次比较
在这里插入图片描述
第三次比较
在这里插入图片描述

上面三次都没有发生向下调整,因为堆顶的元素比它的两个孩子小,但下一次比较会发生向下调整!

第四次比较
在这里插入图片描述
向下调整后,我们就发现,堆顶元素变成了 5。
在这里插入图片描述

第五次比较和第四次比较一样,会发生向下调整。
第五次比较
在这里插入图片描述
第6次比较
在这里插入图片描述

比较完之后,我们会发现我们的堆是这样的
在这里插入图片描述
我们可以发现,7,8,9 就是我们 1 - 9 中 最大的前3个数。

代码实现

那么代码我们就可以这样写。

void PrintTopK(int* data, int len, int k)
{//建立一个堆HP hp;HeapInit(&hp);//插入K个数据for (int i = 0; i < k; i++){HeapPush(&hp, data[i]);}//随后进行比较,是否比栈顶元素大for (int i = k; i < len; i++){if (data[i] > HeapTop(&hp)){//交换位置//把堆顶元素删掉HeapPop(&hp);//Push新数据HeapPush(&hp, data[i]);	}}HeapPrint(&hp);}void TopKTest()
{int n = 50000;//开辟1万个数组 的空间int* a = (int*)malloc(sizeof(int) * n);for (int i = 0; i < n; i++){a[i] = rand() % 1000000; }int k = 10;//给上10个最大值a[20009] = 1000000 + 1;a[30007] = 1000000 + 2;a[20006] = 1000000 + 3;a[10008] = 1000000 + 4;a[10007] = 1000000 + 5;a[16000] = 1000000 + 6;a[10005] = 1000000 + 7;a[40004] = 1000000 + 8;a[30003] = 1000000 + 9;a[10001] = 1000000 + 10;PrintTopK(a,n,k);
}int main()
{srand(time(NULL));TopKTest();return 0;
}

在这里插入图片描述
如果要找10个最小值的话,我们只需要把向上调整和向下调整的条件改一下。
TopK代码

void PrintTopK(int* data, int len, int k)
{//建立一个堆HP hp;HeapInit(&hp);//插入K个数据for (int i = 0; i < k; i++){HeapPush(&hp, data[i]);}//随后进行比较,是否比栈顶元素大for (int i = k; i < len; i++){//大堆,大端找最小值,小端找最大值if (data[i] < HeapTop(&hp))//小堆//if (data[i] > HeapTop(&hp)){//交换位置//把堆顶元素删掉HeapPop(&hp);//Push新数据HeapPush(&hp, data[i]);	}}HeapPrint(&hp);
}

向上调整代码

void AdjustUp(HeapDataType* data, int child)
{//至少比较一次do{int parent = (child - 1) / 2;//大堆if (data[parent] < data[child])//小堆//if (data[parent] > data[child]){//交换Swap(&data[parent],&data[child]);//更新childchild = parent;}else{break;}} while (child > 0);}

向下调整代码

//向下调整
void AdjustDown(HeapDataType* data, int n , int parent)
{//定义左孩子节点int child = parent * 2 + 1;while (child < n){//大堆if (child+1 < n && data[child + 1] > data[child])//小堆//if (child + 1 < n && data[child + 1] < data[child]){child++;}//然后和父亲元比较//大堆if (data[child] > data[parent])//小堆//if ( data[child] < data[parent] ){//位置交换Swap(&data[parent], &data[child]);parent = child;child = parent * 2 + 1;}else{break;}}}

只要把标注了大端小端的if判断改一下,即可切换
在这里插入图片描述

找10个最小值的运行结果
在这里插入图片描述

在这里插入图片描述
用堆实现TopK算法就到这里了,代码的Git链接

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...