什么是TOP—K问题
TOP—K问题:求数据结合中前K个最大的元素或者最小的元素,一般情况下,数据个数远远大于K。
比如:求专业前2名,世界500强,游戏中最活跃的前10名玩家等等。
对于TOP——K问题,首先想到的就是排序,但是,如果数据量非常的大,排序就不太可能了。最佳的方式是用堆解决。
方法思路如下:
1 用数据结合中的前K个元素来建堆。如果是求最大元素,就小堆;如果是最小元素,就大堆。
2 用剩余的元素与堆顶元素比。如果我们求最大的前K个元素,剩余的元素如果大于堆顶,就进堆,如果小于堆顶,就排除。完成全部的遍历后,堆中的元素就是最大的前K个元素了。
接下来,我们要实现一下这个算法。
代码实现
我们找最大的5个数。即K=5.
因为TOP—K的数据比较大,我们这次创造100 000个数据。
void creatdata()
{
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (file == NULL)
{
perror("fopen");
return;
}
for (int i = 0;i < n;++i)
{
int x = (rand() + i) % 100000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
这样,我们就创建了100 000个数据。
接下来,就是建立小堆,完成该算法。
void TOPK(const char*file,int k)
{
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen");
return;
}
//建一个K个数的小堆
//由于vs不支持边长数组,我们只能malloc一下
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc");
return;
}
//从文件中读取前K个数
for (int i = 0;i < k;i++)
{
fscanf(fout, "%d", &minheap[i]);
//建小堆,也就是要向上调整
Adjustup(minheap, i);
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minheap[0])
{
minheap[0] = x;
Adjustdown(minheap, k, 0);
}
}
}
for (int i = 0;i < k;i++)
{
printf("%d\n", minheap[i]);
}
fclose(fout);
}
关于建立小堆,我们也能用向下调整的方法建立小堆。
向下调整建立小堆,也就是从最后一个节点的父亲开始调整。
比如上面这个堆,就是先找到8的父亲23,然后从23开始向下调整,之后,23节点减一到11节点,11节点调整后,11节点减一到10节点,这样一次类推。
于是,上面建立小堆的代码可以这样写:
for (int j = (i - 1) / 2;j >= 0;j--)
{
Adjustdown(minheap, i + 1, 0);
}
这样写的好处是什么呢?
其实,向上调整建立堆的算法的时间复杂度是O(N*logN),而向下调整算法的时间复杂度是O(N)。
可以下去自证一下。
于是,TOP—K问题就算解决了。
完整代码如下:
头文件 heap.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>
void creatdata();
void TOPK(const char*file,int k);
源文件 heap.c
void swap(int* p, int* q)
{
int tmp = *p;
*p = *q;
*q = tmp;
}
void Adjustup(int* arr, int child)//向上调整
{
//int parent = (child - 1) / 2;
while (child > 0)
{
int parent = (child - 1) / 2;
if (arr[child] < arr[parent])
{
swap(&arr[child], &arr[parent]);
child = (child - 1) / 2;
parent = (parent - 1) / 2;
}
else
{
break;
}
//child = (child - 1) / 2;
//parent = (parent - 1) / 2;
}
}
Adjustdown(int* arr, int size, int parent)//向下调整算法
{
//设左孩子,假设左孩子最小
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && (arr[child] > arr[child + 1]))
{
child++;
}
if (arr[parent] > arr[child])
{
swap(&arr[parent], &arr[child]);
//swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void creatdata()
{
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (file == NULL)
{
perror("fopen");
return;
}
for (int i = 0;i < n;++i)
{
int x = (rand() + i) % 100000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void TOPK(const char*file,int k)
{
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen");
return;
}
//建一个K个数的小堆
//由于vs不支持边长数组,我们只能malloc一下
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc");
return;
}
//从文件中读取前K个数
for (int i = 0;i < k;i++)
{
fscanf(fout, "%d", &minheap[i]);
//建小堆,也就是要向上调整
Adjustup(minheap, i);
/*for (int j = (i - 1) / 2;j >= 0;j--)
{
Adjustdown(minheap, i + 1, 0);
}*/
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minheap[0])
{
minheap[0] = x;
Adjustdown(minheap, k, 0);
}
}
}
for (int i = 0;i < k;i++)
{
printf("%d\n", minheap[i]);
}
fclose(fout);
}
大家拜拜!!!