我们继续来延续我们上面的TopK问题,TopK问题一般是在解决有很多数的情况下,我们的k是个和小的值,然后我们是要找到最小或者最大的K个数,这类问题我们也称之为TopK问题,面对这种的问题,如果数字不是很大的情况下,我们就可以写一个循环,然后直接插入堆,进行向上调整就可以解决问题,但是现在这个数很大,我们如果插入数据的话消耗特别大,所以这里就只创建出来k个大小的存储进行建堆,这里我们把数据可以存在我们的文件当中,便于我们进行观察。
首先我们这里给出的问题是在这么多的数据当中找出最大的K个数字,第一个会困扰我们的就是我们应该建立大堆还是小堆呢,答案是
小堆
我们先来分析为什么
首先我们如果是建大堆的话,如果第一次进堆的就是最大的数,那我们这里就会面临的是后面所以的数都进不了堆,我们是不是要比这个堆大才能进堆,这就好比我们的下水管道被堵住了,什么都进不去一样,所以这里是建立小堆,那我们这里还需要就是malloc出来一个k个大小的空间,先进行建堆,因为我们上面讲过建堆可以用向下调整的方式进行建堆,所以这里我们先完成这个步骤。
代码就是下面的这个。
// 建一个k个数小堆
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc error");
return;
}
// 读取前k个,建小堆
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &minheap[i]);
AdjustUp(minheap, i);
}
然后我们给出的规定就是如果比这个堆顶的数字是要大的,我们就直接进行覆盖,堆顶的数就直接变成这个大的数,然后我们向下调整,等所有的数字都进行比较之后,我们也可以取出我们的所有数字了。
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minheap[0])
{
minheap[0] = x;
AdjustDown(minheap, k, 0);
}
}
那因为我们是写在文件当中的,所有我们这里还有一个知识点就是文件操作
我们之前学过文件的读写,也知道要对一个文件进行读写是要先打开我们的文件,如果是写的方式就是会创建,而且没一次都会先清空(如果是有这个文件的话,没有就进行创建)
那打开之后,我们得用fscanf
和fprintf
这两个函数进行,那大家可以查看文档,其实就是和scanf
和printf
有不太一样的。
看这个大家可以回忆起来,要是忘记了可以看之前的文章进行回顾,文章名字就是文件操作
。
那我们后面就是对文章的读写,然后进行操作,完整代码。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
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);
}
void PrintTopK(const char* file, int k)
{
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
// 建一个k个数小堆
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc error");
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 ", minheap[i]);
}
printf("\n");
free(minheap);
fclose(fout);
}
int main()
{
CreateNDate();
PrintTopK("Data.txt", 5);
return 0;
}
当然这里还是需要注意一些东西,比如我们怎么知道那几个数是最大的,我们这里产生的随机数就是和有意思了
保证这些数字都是0-9999999,这里加i也有作用,主要是取决于我们的srand函数产生一定的随机数之后就只会产生相同的随机数了,所有这里的好处就是后面产生的随机数是加上i,因为i也是一直变的,我们只要在产生的随机数里改掉几个比1000000大的数就可以知道我们的代码是不是正确的额,可以直接用数组下标的方式,当然也可以在打开的文件里改,反正都可以来看,还有就是我们的调试窗口。
那我们今天的内容就到此结束了,我们下次再见。