一、前言
在上一篇文章中,我们对建堆的两种方式:向上调整和向下调整,做了时间复杂度的分析。我们发现,向下调整建堆的这种方式时间复杂度最低为 O(N)。接下来,我们要利用堆来进行排序。
从n个数据中选出最大的K个。这里我们使用向下调整的方式建堆且用向下调整的方式完成排序。
二、原理分析
1.要选出最大值,这里我们需要建一个小堆。取n个数据中的前k个数据放入数组中,然后调用向下调整函数。(因为数据是随意的,所以根的左右子树可能不为堆,所以不能从根开始进行向下调整。要重最后一个非叶子结点开始调整,前面关于堆的文章中有细说。)。调整后的k个元素的数组是一个小堆。
2.从剩下的n-k个数据中依次读取数据,与根元素(即数组中最小的)相比较,如果大于根元素,则替换掉根元素,同时进行一次向下调整。调整后的根元素又一次变成最小的数据,
3.重复操作2.最后比较完之后得到的就是n个数据中最大的k个数据。
三、代码实现
1.数据生成
我们需要n个随机的自然数,而且还要有一定的数量和随机性,这里使用一个函数生成1000个随机数,并将它存在文件中。这个代码之前在C语言的文章中有仔细讲过。
void CreateNDate()
{
// 造数据
int n = 10000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (size_t i = 0; i < n; ++i)
{
int x = rand() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
2.将n个数据中的前K个放入数组中
动态开辟k个数组空间,从文件中读入K个数据,放入数组中。
FILE* fout = fopen(filename, "r");//以读的方式打开文件
if (fout == NULL)
{
perror("fopen fail");
exit(-1);
}
//开辟k个元素的空间
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc fail");
exit(-1);
}
//将前k个数据读入数组中
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &minheap[i]);
}
3.向下调整建小堆
从最后一个非叶子结点开始向下调整。注意,这里的向下调整函数需要时建小堆的函数。如果你写的向下调整函数是建大堆的,那么建出来的就是大堆。后面就选不出最大的。
//从最小的非叶子结点开始,以向下调整的方式建小堆
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(minheap, i,k);
}
4.替换根结点,再调整
从剩下的n-k个元素中读取数据与根节点进行比较,比根结点大的则替换根节点,再进行一次向下调整。
//读取数据文件中剩下的n-k个元素,依次与小堆中的元素比较,若比小堆中的元素大,则替换,并向下调整
int number = 0;
while (fscanf(fout, "%d", &number) != EOF)
{
if (minheap[0] < number)
{
minheap[0] = number;
AdjustDown(minheap, 0, k);
}
}
5.将结果打印出来
for (int i = 0; i < k; i++)
{
printf("%d ", minheap[i]);
}
总结
1.造随机数据
2.动态开辟数组并读入k个数据
3.向下调整建立小堆
4.读取剩下的n-k个数据并与根数据进行比较,比根数据大,则替换,同时进行一次向下调整。
注意:获取最大的数据要建小堆,获取最小的数据要建大堆。也就是一个大于符号和小于符号的区别。但是要小心使用。
到这里,关于堆这个数据结构已经讲完了,下一章进入二叉树的链式存储。喜欢的朋友们关注一下~