什么是Topk问题?
Topk问题就是求数据中前k个最大或者最小元素,一般情况下,使用Topk来解决的问题,所面对的数据量都很大。同时Topk可以在空间和时间上都有很好的效率。
解决思路:
①正常情况下,我们求N个数据中前k个最大或最小元素,我们首先要排序数据,然后依次读出前k个最大或最小元素。
但是,如果面对海量的数据,先不说对这些数据排序的消耗有多大;单是能否读取到内存都是问题,所以要处理海量的数据,排序再打印行不通。
②使用建堆+比较的方法
首先,建立k个元素总大小的空间;
其二,将前k个元素存储到这个空间;
其三,将这k个元素建堆,求前k个最大元素——建小堆,求前k个最小元素——建大堆;
其四,将后面k~N个元素依次和堆顶元素比较,如果是大堆情况下,有元素大于堆顶元素,则令该元素覆盖堆顶元素。然后向下调整重新成堆。循环,直到数据随后一个元素。
最后堆中的元素就是这些数据中最大/最小的元素。
代码实现:
①向下调整函数:
void AdjustDown(HeapType* a, int size, int parent)
{
assert(a);//判断传过来的指针是否为空
int child = parent * 2 + 1;//算出左孩子节点
while (child < size)//循环终止条件为孩子节点的下标超出空间大小
{
if (child + 1 < size && 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准备
void CreateData()
{
int n = 1000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen fail");
return;
}
int i = 0;
for (i = 0; i < n; i++)
{
int a = rand() % 10000;
fprintf(fin, "%d\n", a);
}
fclose(fin);
}
③打印数据中前k个最大/最小值(这边以求前k个最大值演示)
void PrintTopk(int k)
{
const char* file = "data.txt";
FILE* fout = fopen(fout, "r");//创建文件指针指向前面创建的存储数据的文件
if (fout == NULL)//判断是否创建成功
{
perror("fopen fail");
return;
}
int* kminheap = (int*)malloc(sizeof(int) * k);//开辟k个元素大小的空间
if (kminheap == NULL)
{
perror("malloc fail");
return;
}
int i = 0;
for (i = 0; i < k; i++)//在k个空间中存储好前k个数据
{
fscanf(fout, "%d", &kminheap[i]);
}
for (i = (k - 1 - 1) / 2; i >= 0; i--)//将存储好的数据向下调整建堆
{
AdjustDown(kminheap, k, i);
}
int val = 0;
while (feof(fout))//循环比较剩余数据,更新堆中的元素,使得堆中元素为当前最大
{
fscanf(fout, "%d", &val);
if (kminheap[0] < val)
{
kminheap[0] = val;
AdjustDown(kminheap, k, 0);
}
}
for (i = 0; i < k; i++)//打印出堆中所有元素,这些元素为数据中最大的k个元素
{
printf("%d ", kminheap[i]);
}
free(kminheap);
kminheap = NULL;
fclose(fout);
}