TOP-K问题:即求数据中前k个最大的元素或者最小的元素,一般情况下,这些数据量是非常大的。
比如:专业前10名、世界500强、世界富豪榜、游戏中前100名等这些排名都是TOP-K问题。
来源于《财富》世界500强排行榜。
对于TOP-k问题,能想到的最简单、最直接的方法就是对数据进行排序,但是在前面我已经提到了,TOP-K问题中的数据量是非常大,大到不能一次性从硬盘读取全部的数据到内存中,那么我们的普通的排序方法就不能进行了,这时,有人就想到的文件外排序,但是TOP-K问题有更好的解决方法。
接下来,我来介绍这种方法的基本思路
1.用数据集合中的前k个元素来建堆(想要读取前k个最大或者最小的元素)
前k个最大的元素,则建小堆
前k个最小的元素,就建大堆
2.用剩余的N-K个元素依次与堆顶的元素来比较,不满足则替换堆顶的元素。如:求前k个最大的元素,则建小堆,然后用剩余的N-K个元素依次与堆顶的元素来比较,如果该新元素比堆顶的元素大,就替换掉堆顶的元素,然后重新建堆,保证堆中元素最小的在堆顶,依次循环下去,当比较完剩余的N-K个元素,数据中前k个最大的元素就全在堆中了。
下面,我先在一个文件中生成一万个随机数,范围在1~1000,再生成10个随机数,范围大于1000,先尝试着求出前10个最大的元素。
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main()
{
int num = 10000;
int Count = 10;
srand(time(NULL));
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen fail\n");
exit(1);
}
for (int i = 0; i < num; ++i)
{
int num1 = rand() % 1000;
fprintf(pf,"%d\n",num1);
if (num1 > 100 && num1 < 300 && Count > 0)//随机插入10个大于1000的数
{
int num2 = rand() % 1000 + 1000;
fprintf(pf, "%d\n", num2);
--Count;
}
}
while (Count > 0)
{
int num2 = rand() % 1000 + 1000;
fprintf(pf, "%d\n", num2);
--Count;
}
return 0;
}
接下来,我来读取出前10个最大的元素。
#include<stdio.h>
#include<stdlib.h>
#define k 10
void swap(int* a,int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//向下调整建堆
void AdjustDown(int* arr)
{
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
int parent = i;
int child = parent * 2 + 1;
while (child < k)
{
if (child + 1 < k && arr[child + 1] < arr[child])
{
++child;
}
if (arr[parent] > arr[child])
{
swap(&arr[parent], &arr[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
}
int main()
{
int arr[k] = { 0 };
FILE* pf = fopen("test.txt","r");
if (pf == NULL)
{
perror("fopen fail\n");
exit(1);
}
//读取k个元素
for (int i = 0; i < k; ++i)
{
fscanf(pf, "%d", &arr[i]);
}
AdjustDown(arr);
int num = 0;
while (fscanf(pf, "%d", &num) != EOF)
{
if (num > arr[0])
{
arr[0] = num;
AdjustDown(arr);
}
}
return 0;
}
运行后数组arr的元素:
由数组arr元素可以得知,该程序成功的挑选出了最大的10个数。
下面,我先在一个文件中生成一万个随机数,范围在100~1000,再生成10个随机数,范围小于100,先尝试着求出前10个最小的元素。(注意在continue前,必须将i加回来,不然生成的数会小于1万)
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main()
{
int num = 10000;
int Count = 10;
srand(time(NULL));
FILE* pf = fopen("test1.txt", "w");
if (pf == NULL)
{
perror("fopen fail\n");
exit(1);
}
for (int i = 0; i < num; ++i)
{
int num1 = rand() % 1000;
if (num1 < 100)
{
continue;
i++;
}
fprintf(pf,"%d\n",num1);
if (num1 < 300 && Count > 0)//随机插入10个大于1000的数
{
int num2 = rand() % 100;
fprintf(pf, "%d\n", num2);
--Count;
}
}
while (Count > 0)
{
int num2 = rand() % 100;
fprintf(pf, "%d\n", num2);
--Count;
}
return 0;
}
接下来,我来读取出前10个最小的元素。
#include<stdio.h>
#include<stdlib.h>
#define k 10
void swap(int* a,int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//向下调整建堆
void AdjustDown(int* arr)
{
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
int parent = i;
int child = parent * 2 + 1;
while (child < k)
{
if (child + 1 < k && arr[child + 1] > arr[child])
{
++child;
}
if (arr[parent] < arr[child])
{
swap(&arr[parent], &arr[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
}
int main()
{
int arr[k] = { 0 };
FILE* pf = fopen("test1.txt","r");
if (pf == NULL)
{
perror("fopen fail\n");
exit(1);
}
//读取k个元素
for (int i = 0; i < k; ++i)
{
fscanf(pf, "%d", &arr[i]);
}
AdjustDown(arr);
int num = 0;
while (fscanf(pf, "%d", &num) != EOF)
{
if (num < arr[0])
{
arr[0] = num;
AdjustDown(arr);
}
}
return 0;
}
运行后数组arr的元素
由数组arr元素可以得知,该程序成功的挑选出了最小的10个数。
下面是代码库
代码库
觉得写的可以的话,三连支持一下。