🗡CSDN主页:d1ff1cult.🗡
🗡代码云仓库:d1ff1cult.🗡
🗡文章栏目:数据结构专栏🗡
TopK问题
在给定的n的数据中,求出这n个数据中最大的k个数字
TopK的代码:
需要注意的是,我们需要建立一个名为data.txt的文件,在里面写入需要进行比较的数据,然后通过fopen,fscanf等函数将文件中的数据读取出来
typedef int HPDataType;
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
// 找出小的那个孩子
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
// 继续往下调整
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void PrintTopK(const char* filename, int k)
{
// 1. 建堆--用a中前k个元素建堆
FILE* fout = fopen(filename, "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &minheap[i]);
}
// 前k个数建小堆
for (int i = (k - 2) / 2; i >= 0; --i)
{
AdjustDown(minheap, k, i);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
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 ", minheap[i]);
}
printf("\n");
free(minheap);
fclose(fout);
}
// fprintf fscanf
void CreateNDate()
{
// 造数据
int n = 10000000;
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) % 10000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
int main()
{
//CreateNDate();
PrintTopK("data.txt", 5);
return 0;
}
TopK详解
这个问题我们是通过建小堆来实现的
1.建小堆
整个TopK问题的核心思想:
Q:啊?这里为什么要建一个小堆而不是一个大堆呢??
A:首先小堆的特性我们都知道,所有的父节点都小于它的左右孩子,也就是说小堆中的根节点是整个树中 值最小的。那么很好,比如我们要比较前20个数据里面最大的8个数据。
那我们需要建一个结点数为8的小堆。然后将20个数据一个一个的进入小堆,并进行向下调整使其再次成为一个小堆,每插入一个数据都会进行一次向下调整使得创建的这个堆为小堆
也就能保证根节点永远是最小的,插入8个数据之后,后面插入的数据都要与这8个数形成的堆(小堆)的根节点进行比较,如果数据大于根节点,那么堆的根节点的值替换为插入的数据,然后再次进行向下调整,将堆中最小的数据放在根节点的位置上,如此循环,最后堆中会剩下20个数字中最大的8个数字
建了一个8个数据的小堆。然后让剩下数据依次比较,比根节点小就不管了,比根节点大的话就与根节点交换,并且进行向下调整。
2.详解
本来有这样一个数组,我们想求这个数组里面最大的8个数字
那我们先将前8个数字建小堆
然后我们将剩余的数据与根节点进行比较,下一个数字是8,大于根节点,8赋值给根节点,然后进行向下调整。
替换后:
向下调整后:
下面是这一段的代码实现
// 前k个数建小堆
for (int i = (k - 2) / 2; i >= 0; --i)
{
AdjustDown(minheap, k, i);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minheap[0])
{
// 替换你进堆
minheap[0] = x;
AdjustDown(minheap, k, 0);
}
}
如此循环,最后就能的到存了最大的8个数据的堆。
以上便是TopK问题了。