🚗堆的知识点直通车:【初阶数据结构-二叉树】
1)什么是TOPK
在实际的软件开发过程和系统设计中,我们通常要从海量数据中选出最大最小,例如世界500强,专业前10名,购物软件中,我们要参考销量最高,最新发布,价格高低等等;TOPK问题即是:从N个数据中选出最大(小)的K个数据。
1.1)TOPK的特点
TOPK问题中的N通常非常大,k通常比较小,如N=1000,k=10;
2)选出最大(小)为什么要使用堆
对于初学者来说,最容易想到的就是全局排序
2.1)方案对比
方案一:全局排序
- 思路:将N个数据全部读入内存,进行快速排序,然后取前K个
- 时间复杂度:O(
)
- 缺点:
- 效率低:当N无限大,
增长虽然缓慢,但整体计算量依旧巨大
- 内存限制:这是最致命的,如果N=100亿,数据量所占内存就可能高达几十GB,普通的计算机内存根本就无法一次性存储所有的数据。
方案二:堆排序(HeapSort)------TOPK 的最优解
- 思路:维护一个大小为k的堆
- 时间复杂度:O(
):K远小于N,所以
可以看成一个常数,远远优于全局排序
- 空间复杂度:O(K),我们只需要K个大小的空间来储存堆,无论堆有多大(甚至可以是无限数据流),我们只需要极小的空间就能存储堆
3)核心原理:选大堆还是小堆?
首先记住一个口诀:
找出最大K个数:建小堆
找出最小K个数:建大堆
3.1)深度解析:为什么找最大建小堆
假设我们要从10000个数中找最大的前10个:
- 构建堆进行初始化:建一个大小为10的堆,将前10个数挪进去,调整为小堆,此时堆顶数据就是这10个数中最小的
- 遍历剩余数据:依次读取剩下的x数据
- 比较与替换:
- 如果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个数,那么就可以验证我们的排序是正确的。