堆排序之“TOP-K”问题

目录

一、什么是TOP-K问题

二、解决思路

一般的正常思路:

最优的解决思路:

三、文件流中实践TOP-K方法

创建包含足够多整数的文件:

找出最大的K个数

完整版代码:


前面我已经学习过使用"堆排序"对数组排降序了,接下来再来看一个堆排序的应用场景。

一、什么是TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大,即:N个数找最大的前K个。

二、解决思路

一般的正常思路:

把这N个数建成大堆,Pop弹出K次堆顶,即可找出最大的前K个数,但是有些场景这种思路解决不了,例如N非常大时,假设N是10亿,10亿个数建堆所需的空间我们来计算一下:

一个整型变量需要四个字节空间,10亿个整型数据需要40亿个字节,1G可以放10亿字节,所以我们需要 4G空间为10亿个整型数据建堆。

4G感觉不多的话,如果一百亿数据呢?一千亿呢?

内存无法承载这么大的空间时,数据会存储到磁盘上,磁盘的效率比内存慢很多,所以这种方法如果数据过多,就无法再内存上快速找到TOP-K。

最优的解决思路:

  1. 前K个数建小堆。
  2. 后面N-K个数,依次比较,如果比堆顶的数据大,就替换它进堆,进堆后向下调整。
  3. 最后这个小堆的值就是最大的前K个。

三、文件流中实践TOP-K方法

我们来在文件中实践一下这个方法:

创建包含足够多整数的文件:

cpp 复制代码
void CreateNData()
{
	int n = 100000;
	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() % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
  • 定义一个变量n,表示要生成的随机整数的数量为十万个。
  • 使用srand函数设置随机数种子,time(0)返回当前时间的秒数,确保每次运行程序生成的随机数序列都不同。
  • 定义一个文件名file,表示要生成的文件名。
  • 使用fopen函数打开文件,以写入模式打开,如果没有文件,则创建一个,如果文件打开失败,输出一个错误信息并返回。
  • 使用for循环生成n个随机整数,并使用fprintf函数将它们写入文件中。
  • 使用fclose函数关闭文件。

找出最大的K个数

如果建小堆的方法---向下调整 忘记了,不妨看看这篇文章 向下调整的部分

cpp 复制代码
void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}
	
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			AdjustDown(kminheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}

代码较长进行分段讲解:

cpp 复制代码
    const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}
    for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}
  • 定义一个文件名file,表示要读取的文件名。
  • 使用fopen函数打开文件,以读取模式打开,如果文件打开失败,输出一个错误信息并返回。
  • 使用malloc函数动态分配一个大小为k的整数数组 kminheap,用于存储最大的 k 个数,如果内存分配失败,输出一个错误信息并返回。
  • 使用for循环从文件中读取前k个整数,并将它们存储到kminheap数组中。
cpp 复制代码
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			AdjustDown(kminheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
  • 使用向下调整函数将kminheap数组构建成一个小堆(从小到大)。
  • feof(fout)是一个C标准库函数,用于判断文件指针fout所指向的文件是否已经到达文件末尾。该函数的返回值为非零值表示已经到达文件末尾,返回值为0表示文件还没有到达末尾。
  • 使用while循环从文件中读取剩余的N-K整数,如果某个整数比堆顶元素大,就将它替换堆顶元素,并使用AdjustDown函数将堆重新调整为小堆,
  • 因为我们需要前K个最大的数,而建的小堆也是K个元素,所以这种操作可以得到由最大前K个元素构成的小堆。
  • 使用for循环输出堆中的所有元素。
  • 使用fclose函数关闭文件。
    我们来测试一下,首先我在主函数调用CreateNData函数对文件data.txt写入文件。
cpp 复制代码
int main()
{
	CreateNData();
	return 0;
}

然后在文件中对五个数添加几位使其成为最大的五个数。
这是屏蔽掉 CreateNData 函数,防止重新生成数字覆盖我修改的数字,调用PrintTopK函数输出5个最大的数。

cpp 复制代码
int main()
{
	//CreateNData();
	PrintTopK(5);
	return 0;
}

输出结果为:

完整版代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size) {
		if (child + 1 < size && 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 CreateNData()
{
	int n = 100000;
	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() % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}
	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			AdjustDown(kminheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
	fclose(fout);
}

int main()
{
	CreateNData();
	PrintTopK(5);
	return 0;
}
相关推荐
时光の尘2 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
-一杯为品-11 分钟前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
Lenyiin1 小时前
02.06、回文链表
数据结构·leetcode·链表
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
爱摸鱼的孔乙己1 小时前
【数据结构】链表(leetcode)
c语言·数据结构·c++·链表·csdn
Dola_Pan1 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
IU宝2 小时前
C/C++内存管理
java·c语言·c++
qq_459730032 小时前
C 语言面向对象
c语言·开发语言
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法