Top-K问题——堆的应用

一、问题介绍

        Top-K问题是一个堆结构的经典问题,要求我们对给出的一组数据选出其中最大或者最小的k个元素。在拿到这个问题时,第一反应肯定都是排序,然后取所需要的k个元素即可。但是Top-K问题的数据量一般都很大,对其进行排序只为找到那短短k个元素,这个性价比怎么想怎么不甘心。除此之外,我们所学的排序算法多为内排序,当数据量大到一定程度时甚至有可能内存中脸数据都放不下。那么有没有其他方法可以更好地解决这个问题呢?

        这时,我们就可以搬出刚刚学过的堆来解决这个问题了。建立一个k个元素大小的堆来对数据进行入堆出堆判断,最后大浪淘沙得到的就是我们所需要的k个元素。

二、问题详解

1.构造模拟数据

        因为我们需要大量的数据,所以我们可以使用随机数产生数据,同时由于数据量较大,我们采用文件的方式将其存储起来。我们在构造数据时,刻意将一些位置加上特定的值,使得他们的值一定位列最大者其中,这将便于我们对结果进行正误判断。

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		if (i == 298)
		{
			x += 1000000;
		}
		if (i == 28)
		{
			x += 2000000;
		}
        if (i == 1298)
		{
			x += 3000000;
		}
        if (i == 2298)
		{
			x += 4000000;
		}
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

2.堆的选择

        在处理最大k个和最小k个这两个不同的问题时,我们对大堆和小堆的选择肯定是有所不同的。

        在草率做出抉择之前,我们需要先明确我们要用堆来干什么。我们建堆的目的是筛选最大的k个值,那能否成为最大的k个值取决于谁呢?显然,能否是最大的k个值要看它和现在k个值中最小者的关系,如果它更大就可以挤掉这个最小者从而成为其中一员。所以当针对最大的k个值时,我们需要堆可以很明显的为我们展示出topk的门槛,即堆中的最小值,那么我们就毫无疑问地选择小堆了,因为小堆堆顶正是最小值。

Top-K问题——堆的应用_第1张图片

        同理,要选取最小的k个值,门槛在于堆中的最大值,所以需要使用大堆。

3.思路与代码

        创建一个k个元素的数组作为堆,然后读取文件中前k个元素组成堆,这时采用向上调整建堆即可。然后对后续元素一一与堆顶比较,如果大于堆顶,则取代堆顶,再进行向下调整,保证堆的特性。否则就继续比较下一个元素。如此最后所得到的堆中的数据即为最大的k个数据。

void PrintTopK(int k)
{
	FILE* fou = fopen("data.txt", "r");
	if (fou == NULL)
	{
		perror("fopen fail");
		return;
	}
	//建堆
	int* a = (int*)malloc(sizeof(int) * k);
	if (a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(fou, "%d", &a[i]);
		AdjustUpMin(a, i);
	}
	int num = 0;
	while (fscanf(fou, "%d", &num) != EOF)
	{
		if (num > a[0])
		{
			a[0] = num;
			AdjustDownMin(a, k, 0);
		}
	}
	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	fclose(fou);
}

int main()
{
	CreateNDate();
	PrintTopK(10);
	return 0;
}

你可能感兴趣的:(数据结构)