《堆的应用》TOP-K问题

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

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个数。

下面是代码库
代码库

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

### 原理 Top - K算法的核心目标是从一个数据集合中找出最大(或最小)的K个元素。其基本原理基于排序或的思想。 基于排序的方法是对整个数据集合进行排序,然后取排序后序列的前K个元素。例如,若要找出最大的K个元素,将数据降序排序后,前K个即为所求。不过,这种方法的时间复杂度较高,为$O(n log n)$,其中n是数据集合的大小。 基于的方法更为高效。以找出最大的K个元素为例,构建一个大小为K的最小。遍历数据集合,当的元素数量小于K时,直接将元素插入中;当的元素数量达到K后,若当前元素大于顶元素,则将顶元素替换为当前元素,并重新调整以维护最小的性质。最终中的K个元素就是最大的K个元素。这种方法的时间复杂度为$O(n log K)$,在数据量较大时优势明显。 ### 实现 以下是使用Python实现基于Top - K算法的代码示例: ```python import heapq def top_k(arr, k): if k == 0: return [] min_heap = [] for num in arr: if len(min_heap) < k: heapq.heappush(min_heap, num) elif num > min_heap[0]: heapq.heapreplace(min_heap, num) return sorted(min_heap, reverse=True) # 示例使用 data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] k = 3 result = top_k(data, k) print(result) ``` ### 应用 - **搜索引擎**:在搜索结果排序中,搜索引擎需要从大量的网页中找出与用户查询最相关的K个网页展示给用户。使用Top - K算法可以高效地完成这一任务。 - **推荐系统**:推荐系统要根据用户的历史行为和偏好,从海量的物品中为用户推荐K个最可能感兴趣的物品。通过计算物品与用户的相关性得分,利用Top - K算法找出得分最高的K个物品。 - **数据分析**:在数据分析中,可能需要找出数据集中最大或最小的K个值,用于异常检测、趋势分析等。例如,找出销售额最高的K个产品,以便重点关注和推广。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值