TOP-K问题
TOP-K
问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
比如:
要存储4GB的内存数据,现在只有1GB,怎么找到里面最大的10个数?
分四次建堆,每次建堆分别找到堆里面最大的10个数,最后在40个数里找到最大的10个数。
如果只有1KB怎么办呢?
对于Top-K
问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
用数据集合中前
K
个元素来建堆要取前
k
个最大的元素,则建小堆要取前
k
个最小的元素,则建大堆用剩余的
N-K
个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余
N-K
个元素依次与堆顶元素比完之后,堆中剩余的K
个元素就是所求的前K
个最小或者最大的元素
为什么要取前
k
个最大的元素,则建小堆呢?因为在最小堆中,堆顶元素总是堆中最小的元素。当处理一个新元素时,如果这个新元素比堆顶元素大,那么它就有可能是最大的
k
个数之一。此时,我们把堆顶元素弹出,将新元素加入堆中。然后调整新元素在堆里面的位置。这样,堆中始终保持了当前遇到的最大的k
个数。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//生成随机数
//void CreateNDate()
//{
// // 造数据
// int n = 100000;
// srand(time(0));
// const char* file = "data.txt";
// FILE* fin = fopen(file, "w");
// if (fin == NULL)
// {
// perror("fopen error");
// return;
// }
// for (int i = 0; i < n; ++i)
// {
// int x = (rand() + i) % 1000000;
// fprintf(fin, "%d\n", x);
// }
// fclose(fin);
//}
//定义堆的结构---数组
typedef int HPDataType;
typedef struct Heap {
HPDataType* arr;
int size;//有效的数据大小
int capacity;//空间大小
}HP;
//交换函数
void Swap(int* x, int* y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
//堆的向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n) {
int child = parent * 2 + 1;//左孩子
while (child < n) {
//小堆:找左右孩子中最小的
//大堆:找左右孩子中最大的
//看arr[child] > arr[child + 1]和if (arr[child] < arr[parent])里面的符号,符号不变就是小堆,反过来就是大堆
if (child + 1 < n && arr[child] > arr[child + 1]) {//防止越界
child++;
}
if (arr[child] < arr[parent]) {
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
//TOP-K排序
void topk()
{
printf("请输入k:>");
int k = 0;
scanf("%d", &k);
//从文件中读取前k个数据,建堆
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
int val = 0;
int* minheap = (int*)malloc(sizeof(int) * k);//创建小堆
if (minheap == NULL)
{
perror("malloc error");
return;
}
for (int i = 0; i < k; i++)//循环读取数据,先读取k个
{
fscanf(fout, "%d", &minheap[i]);
}
// 建k个数据的小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)//循环读取数据,读取n-k个
{
AdjustDown(minheap, i, k);
}
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
// 读取剩余数据,比堆顶的值大,就替换他进堆
if (x > minheap[0])
{
minheap[0] = x;
AdjustDown(minheap, 0, k);
}
}
for (int i = 0; i < k; i++)//打印最大的k个数据
{
printf("%d ", minheap[i]);
}
fclose(fout);
}
int main() {
//CreateNDate();
topk();
return 0;
}
时间复杂度:O(n) = k + (n − k)log2k