【C语言实现堆的TOPK问题】——海量数据中怎么找前K大(小)?

🚗堆的知识点直通车:【初阶数据结构-二叉树

1)什么是TOPK

在实际的软件开发过程和系统设计中,我们通常要从海量数据中选出最大最小,例如世界500强,专业前10名,购物软件中,我们要参考销量最高,最新发布,价格高低等等;TOPK问题即是:从N个数据中选出最大(小)的K个数据。

1.1)TOPK的特点

TOPK问题中的N通常非常大,k通常比较小,如N=1000,k=10;

2)选出最大(小)为什么要使用堆

对于初学者来说,最容易想到的就是全局排序

2.1)方案对比

方案一:全局排序

  • 思路:将N个数据全部读入内存,进行快速排序,然后取前K个
  • 时间复杂度:O(
  • 缺点:
  1. 效率低:当N无限大,增长虽然缓慢,但整体计算量依旧巨大
  2. 内存限制:这是最致命的,如果N=100亿,数据量所占内存就可能高达几十GB,普通的计算机内存根本就无法一次性存储所有的数据。

方案二:堆排序(HeapSort)------TOPK 的最优解

  • 思路:维护一个大小为k的堆
  • 时间复杂度:O():K远小于N,所以可以看成一个常数,远远优于全局排序
  • 空间复杂度:O(K),我们只需要K个大小的空间来储存堆,无论堆有多大(甚至可以是无限数据流),我们只需要极小的空间就能存储堆

3)核心原理:选大堆还是小堆?

首先记住一个口诀:

找出最大K个数:建小堆

找出最小K个数:建大堆

3.1)深度解析:为什么找最大建小堆

假设我们要从10000个数中找最大的前10个:

  1. 构建堆进行初始化:建一个大小为10的堆,将前10个数挪进去,调整为小堆,此时堆顶数据就是这10个数中最小的
  2. 遍历剩余数据:依次读取剩下的x数据
  3. 比较与替换:
  • 如果x>堆顶元素的数据,将堆顶元素替换成x,重新向下调整为小堆
  • 如果x<=堆顶元素的数据,直接舍弃继续遍历后续数据;可以将堆顶元素看成入堆的门槛,我们要找的是最大的10个数,如果连最小的堆顶元素都比不过,那它一定不是最大的10个数

4.结果:遍历完所有的数据,留在堆里的就是最大的10个数

3.2)算法图解

假如说有一组数据[5,7,8,2,4,9,6,1,3,10],找出最大的三个数:

最终结果:堆中元素[10,9,8]是最大的三个数

4)整体代码实现(手撕级别)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//交换两个整数
void Swap(int* x,int* y)
{
    int tmp;
    int tmp=*x;*x=*y;*y=tmp;

}
//向下调整算法,用来维护堆
//n是堆中有效元素
//parent父亲节点
void AdustDown(int* a,int n,int parent)
{
    int chila=parent*2+1;
    while(child<n)
    {
        if(child+1<n&&a[child+1]<a[child])
            ++child;
        if(child<n&&a[child]<a[parent])
        {
            Swap(&a[child],&a[parent]);
            parent=child;
            chila=parent*2+1;
        }
        else
            break;//如果孩子比父亲大,则满足小堆退出
    }

}
创建数据库
//void CreatNumber()
{
    //创建一个文件用于存储数据
    //内存空间小,以文件形式直接存储,是存储到硬盘容纳空间更大
    const char* file="data.txt";
    FILE* fin=fopen(file,"w");
    //随机生成10000个数据
    for(int i=0;i<n;i++)
    {
        int x=(rand()+i)%10000;//随机生成小于10000的数
        fprintf(fin,%d/n,x);
    }
    fclose(fin);
}

//TOPK
void TOPK()
{
    //创建大小为k的堆
    int k=0;
    printf("请输入k 的值:>");
    scanf("%d",&k);
    int* kmaxHeap=(int*)malloc(sizeof(int)*k);
    const char* file="data.txt";
    FILE* fout=fopen(file,"r");
    if(fout==NULL)
    {
        perror("open fail");
        exit(0);
    }

    //先将前k个入数组
    for(int i=0;i<k;i++)
    {
        fscanf(fout,%d,&kmaxHeap[i]);
    }

    //进行调整建堆,从最后一个非叶子节点开始
    for(int i=(k-1-1)/2;i>0;i--)
    {
        AdustDown(kmaxHeap,k,i);
    }

    //依次遍历文件剩下N-K个
    int x=0;
    while(fscanf(fout,%d,x)>0)//遇到文件末尾停止
    {
        if(x>kmaxHeap[0])
        {
            kmaxHeap[0]=x;
            AdustDown(kmaxHeap,k,0);
        }
    }
    //打印前k个数
    printf("最大的前%d个数依次是:>",k);
    for(int j=0;j<k;j++)
    {
        printf("%d ",kmaxHeap[j]);
    }

}

如何更快的测试?假如我们选出10000个数里最大的10个数,我们在文件里更改10个数,让这10个数大于10000,如果排序出来是这10个数,那么就可以验证我们的排序是正确的。

相关推荐
代码游侠2 小时前
C语言核心概念复习(三)
开发语言·数据结构·c++·笔记·学习·算法
Herbert_hwt2 小时前
数据结构与算法绪论:为何学、学什么、如何避坑
c语言·数据结构·算法
宵时待雨2 小时前
数据结构(初阶)笔记归纳10:二叉树
数据结构·笔记·算法
数智工坊2 小时前
【数据结构-查找】7.1顺序查找-折半查找-分块查找
数据结构
后来后来啊2 小时前
2026.2.2 & 2.3学习笔记
数据结构·笔记·学习·算法·leetcode
Emberone2 小时前
数据结构:算法的时间复杂度和空间复杂度
数据结构·算法
历程里程碑3 小时前
Linux 18 进程控制
linux·运维·服务器·开发语言·数据结构·c++·笔记
EnglishJun3 小时前
数据结构的学习(三)---双向链表与循环链表
数据结构·学习·链表
遨游xyz3 小时前
数据结构-栈
java·数据结构·算法