海量数据中找出前k大数(topk问题),一篇文章教会你

博客内容:【数据结构】向上调整建堆和向下调整建堆的天壤之别以及堆排序算法

作  者:陈大大陈

个人简介:一个正在努力学技术的准前端,专注基础和实战分享 ,欢迎私信!

欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信

目录

什么是TOPK问题?

TOPK问题的必要性和详细思路 

 包含TOPK算法的堆的源代码 

TOPK算法复杂度分析 

什么是TOPK问题?

TOP-K问题:在数据量比较大的情况下求数据集合中前K个最大的元素或者最小的元素,

比如:世界500强的企业、世界富豪榜、游戏中前100的氪金玩家等。

TOPK问题的必要性和详细思路 

N个数寻找最大的前k个。

正常思路是把这N个数建成堆,然后 pop k次,即可找到最大的前k个值。

但是有些场景,上面的思路解决不了,比如N非常大的情况。

假设N是10亿,k是100,就不可能运行出结果。

因为如果这样建成堆的话,所占用的空间太大了。

10亿个整数需要多大的空间?答案是4GB。

4GB的内存啥电脑来了也吃不消,再说100亿,1000亿呢?

可见这不是一个可行的方法,咱们另辟蹊径。

我们知道,当数据很大时,它不会存在内存上,而是转而储存在磁盘文件里,所以我们的思路就转向磁盘文件。

磁盘中能建堆吗?答案是不能,因为磁盘不能随机访问。

解决思路,前K个数建小堆,让其它N-K个数和堆顶比较,如果比它大就替换它进堆。

替换它进堆的意思就是,将堆顶元素替换,然后将其向下调整。

最后这个小堆的内容就是最大的前K个。

向下调整算法我上篇文章介绍过,大家不了解的可以看看。

(4条消息) 【数据结构】向上调整建堆和向下调整建堆的天壤之别以及堆排序算法_陈大大陈的博客-CSDN博客

考虑到要排序的数据可能很多,我们建立一个TXT文件来存放待排序数据。

为了创建足够多的随机数,我们使用srand函数。

void CreateNDate()
{
	// 造数据
	int n = 1000;
	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() % 10000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

 关于文件的操作,大家有忘记的可以看我之前的博客。

写程序必会的C语言文件操作(上)附手绘图详解_陈大大陈的博客-CSDN博客

运行代码。

海量数据中找出前k大数(topk问题),一篇文章教会你_第1张图片

上面我们创建了一个data文件,并且往里面存入了1000个随机数。 

海量数据中找出前k大数(topk问题),一篇文章教会你_第2张图片

 用上面的思路将其排序。

首先是建堆,用a中前k个元素建小堆。

然后将剩余n-k个元素依次与堆顶元素交换,不满则则替换。

替换元素之后不要忘记向下调整。

void PrintTopK(int k)
{
	// 1. 建堆--用a中前k个元素建小堆
	int* topk = (int*)malloc(sizeof(int) * k);
	assert(topk);
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读出前k个数据建小堆
	for (int i = 0; i < k; ++i)
	{
		fscanf(fout, "%d", &topk[i]);
	}

	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(topk, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int val = 0;
	int ret = fscanf(fout, "%d", &val);
	while (ret != EOF)
	{
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}
		ret = fscanf(fout, "%d", &val);
	}

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

	free(topk);
	fclose(fout);

}

我们人工在data文件里面建立几个大数,运行代码。

 成功打印出我们添加的几个大数,排序成功 。

 包含TOPK算法的堆的源代码 

#pragma once
#include
#include
#include
#include
#include
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
//int HeapSize(HP* php);
void Swap(HPDataType* p1, HPDataType* p2);
void HeapSort(int* a, int n);
void HeapDestroy(HP* php);
void CreateNDate();
void PrintTopK(int k); 
#define _CRT_SECURE_NO_WARNINGS
#include"heap.h"
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1< n && 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 AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		{	
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			 break;
		}
	}
}
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

 	php->a[php->size] = x;
	php->size++;

    	AdjustUp(php->a, php->size - 1);
}
void HeapPop(HP* php)
{
	assert(php);
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--; 
	AdjustDown(php->a, php->size,0);
}
HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

//void HeapSort(int* a, int n)
//{
//	HP hp;
//	HeapInit(&hp);
//	for (int i = 0; i < n; i++)
//	{
//		HeapPush(&hp, a[i]);
//	}
//	int i = 0;
//	while (!HeapEmpty(&hp))
//	{
//		int top = HeapTop(&hp);
//		a[i++] = top;
//		HeapPop(&hp);
//	}
//	HeapDestroy(&hp);
//}
//换出我们想要的结构,向上建立小堆,然后删除元素,最后建立大堆 1 3 5 4 5 9 7 8
//建小队回损坏结构
//向下调整算法可以帮我们完成工作
//升序-》建大堆    降序-》建小堆

void HeapSort(int* a, int n)
{
	int end = n-1;
	//向上调整建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

	//向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	while (end>0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a,end, 0);
		--end;
	}
}
void CreateNDate()
{
	// 造数据
	int n = 1000;
	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() % 10000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}
void PrintTopK(int k)
{
	// 1. 建堆--用a中前k个元素建小堆
		int* topk = (int*)malloc(sizeof(int) * k);
	assert(topk);
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读出前k个数据建小堆
	for (int i = 0; i < k; ++i)
	{
		fscanf(fout, "%d", &topk[i]);
	}

	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(topk, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int val = 0;
	int ret = fscanf(fout, "%d", &val);
	while (ret!=EOF)
	{
		
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}
		ret = fscanf(fout, "%d", &val);
	
	}

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

	free(topk);
	fclose(fout);

}
//test.c
#define _CRT_SECURE_NO_WARNINGS
#include"heap.h"
void UpSort(HPDataType* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
}
int main()
{
	HP hp;
	HeapInit(&hp);
	int a[] = { 7,8,4,6,3,0,1,2,5,9 };
	//HeapSort(a, sizeof(a) / sizeof(a[0]));
	int n = sizeof(a) / sizeof(int);



	HeapSort(a, sizeof(a) / sizeof(a[0]));
	/*for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		HeapPush(&hp, a[i]);
	}
	while (!HeapEmpty(&hp))
	{
		printf("%d\n", HeapTop(&hp));
		HeapPop(&hp);
	}*/
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	//CreateNDate();
	PrintTopK(5);
	return 0;
}

TOPK算法复杂度分析 

​ 一开始需要对K个元素进行建堆,时间复杂度为O(k),之后遍历数组,最坏的情况是,每个元素都与堆顶比较并排序,需要调整n次 复杂度是O(nlog(k)),因此总复杂度是O(k+nlog(k))

如有错误欢迎大佬评论区指正。

欢迎转载,烦请署名并保留原文链接。

海量数据中找出前k大数(topk问题),一篇文章教会你_第3张图片

你可能感兴趣的:(数据结构,pycharm,python,开发语言,算法)