《堆的应用》TOP-K问题

文章介绍了如何利用堆排序算法有效地解决大数据集中的TOP-K问题,即找出数据集中前K个最大或最小的元素。首先通过建立小堆或大堆来存储前K个元素,然后遍历剩余数据,与堆顶元素比较并替换,确保堆的性质。通过示例代码展示了如何生成随机数文件并读取,找出其中最大的10个或最小的10个数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TOP-K问题:即求数据中前k个最大的元素或者最小的元素,一般情况下,这些数据量是非常大的。

比如:专业前10名、世界500强、世界富豪榜、游戏中前100名等这些排名都是TOP-K问题。
在这里插入图片描述
来源于《财富》世界500强排行榜。

对于TOP-k问题,能想到的最简单、最直接的方法就是对数据进行排序,但是在前面我已经提到了,TOP-K问题中的数据量是非常大,大到不能一次性从硬盘读取全部的数据到内存中,那么我们的普通的排序方法就不能进行了,这时,有人就想到的文件外排序,但是TOP-K问题有更好的解决方法。

接下来,我来介绍这种方法的基本思路

1.用数据集合中的前k个元素来建堆(想要读取前k个最大或者最小的元素)
前k个最大的元素,则建小堆
前k个最小的元素,就建大堆
2.用剩余的N-K个元素依次与堆顶的元素来比较,不满足则替换堆顶的元素。如:求前k个最大的元素,则建小堆,然后用剩余的N-K个元素依次与堆顶的元素来比较,如果该新元素比堆顶的元素大,就替换掉堆顶的元素,然后重新建堆,保证堆中元素最小的在堆顶,依次循环下去,当比较完剩余的N-K个元素,数据中前k个最大的元素就全在堆中了。

下面,我先在一个文件中生成一万个随机数,范围在1~1000,再生成10个随机数,范围大于1000,先尝试着求出前10个最大的元素。

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main()
{
	int num = 10000;
	int Count = 10;
	srand(time(NULL));
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fail\n");
		exit(1);
	}
	for (int i = 0; i < num; ++i)
	{
		int num1 = rand() % 1000;
		fprintf(pf,"%d\n",num1);

		if (num1 > 100 && num1 < 300 && Count > 0)//随机插入10个大于1000的数
		{
			int num2 = rand() % 1000 + 1000;
			fprintf(pf, "%d\n", num2);
			--Count;
		}
	}
	while (Count > 0)
	{
		int num2 = rand() % 1000 + 1000;
		fprintf(pf, "%d\n", num2);
		--Count;
	}
	return 0;
}

接下来,我来读取出前10个最大的元素。

#include<stdio.h>
#include<stdlib.h>
#define k 10
void swap(int* a,int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//向下调整建堆
void AdjustDown(int* arr)
{
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		int parent = i;
		int child = parent * 2 + 1;
		while (child < k)
		{
			if (child + 1 < k && arr[child + 1] < arr[child])
			{
				++child;
			}
			if (arr[parent] > arr[child])
			{
				swap(&arr[parent], &arr[child]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
}	
int main()
{
	int arr[k] = { 0 };
	FILE* pf = fopen("test.txt","r");
	if (pf == NULL)
	{
		perror("fopen fail\n");
		exit(1);
	}

	//读取k个元素
	for (int i = 0; i < k; ++i)
	{
		fscanf(pf, "%d", &arr[i]);
	}
	AdjustDown(arr);
	
	int num = 0;
	while (fscanf(pf, "%d", &num) != EOF)
	{
		if (num > arr[0])
		{
			arr[0] = num;
			AdjustDown(arr);
		}
	}
	return 0;
}

运行后数组arr的元素:
在这里插入图片描述

由数组arr元素可以得知,该程序成功的挑选出了最大的10个数。

下面,我先在一个文件中生成一万个随机数,范围在100~1000,再生成10个随机数,范围小于100,先尝试着求出前10个最小的元素。(注意在continue前,必须将i加回来,不然生成的数会小于1万)

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main()
{
	int num = 10000;
	int Count = 10;
	srand(time(NULL));
	FILE* pf = fopen("test1.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fail\n");
		exit(1);
	}
	for (int i = 0; i < num; ++i)
	{
		int num1 = rand() % 1000;
		if (num1 < 100)
		{
			continue;
			i++;
		}
		fprintf(pf,"%d\n",num1);

		if (num1 < 300 && Count > 0)//随机插入10个大于1000的数
		{
			int num2 = rand() % 100;
			fprintf(pf, "%d\n", num2);
			--Count;
		}
	}
	while (Count > 0)
	{
		int num2 = rand() % 100;
		fprintf(pf, "%d\n", num2);
		--Count;
	}
	return 0;
}

接下来,我来读取出前10个最小的元素。

#include<stdio.h>
#include<stdlib.h>
#define k 10
void swap(int* a,int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//向下调整建堆
void AdjustDown(int* arr)
{
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		int parent = i;
		int child = parent * 2 + 1;
		while (child < k)
		{
			if (child + 1 < k && arr[child + 1] > arr[child])
			{
				++child;
			}
			if (arr[parent] < arr[child])
			{
				swap(&arr[parent], &arr[child]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
}	
int main()
{
	int arr[k] = { 0 };
	FILE* pf = fopen("test1.txt","r");
	if (pf == NULL)
	{
		perror("fopen fail\n");
		exit(1);
	}

	//读取k个元素
	for (int i = 0; i < k; ++i)
	{
		fscanf(pf, "%d", &arr[i]);
	}
	AdjustDown(arr);
	
	int num = 0;
	while (fscanf(pf, "%d", &num) != EOF)
	{
		if (num < arr[0])
		{
			arr[0] = num;
			AdjustDown(arr);
		}
	}
	return 0;
}

运行后数组arr的元素
在这里插入图片描述
由数组arr元素可以得知,该程序成功的挑选出了最小的10个数。

下面是代码库
代码库

觉得写的可以的话,三连支持一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值