海量数据求top K

40 篇文章 7 订阅

top K问题:给定10000个整数,找出前10大的元素

一、大根堆/小根堆

先用前10个整数创建一个小根堆(最小值就在堆顶),然后遍历剩下的整数,如果整数比堆顶元素大,那么堆顶元素出堆,然后再把整数入堆,遍历完所有整数,小根堆里面放的就是值最大的前10个元素了

大根堆淘汰大的,用于找top K小的;小根堆淘汰小的,用于找top K大的。如果找的是第k小(大根堆堆顶)或者第k大(小根堆堆顶),只需要访问堆顶一个元素就可以了

int main(){
	vector<int> vec;
	for (int i = 0; i < 10000; ++i){
		vec.push_back(rand() + i);
	}

	//算法的时间复杂度:O(n)
	//定义小根堆  priority_queue<int> maxHeap;
	//优先级队列默认实现是大根堆,用函数对象greater改变排序方式
	priority_queue<int, vector<int>, greater<int>> minHeap;

	//先往小根堆放入10个元素
	int k = 0;
	for (; k < 10; ++k)	{
		minHeap.push(vec[k]);
	}

	/*
	遍历剩下的元素依次和堆顶元素进行比较,如果比堆顶元素大,
	那么删除堆顶元素,把当前元素添加到小根堆中,元素遍历完成,
	堆中剩下的10个元素,就是值最大的10个元素
	*/
	for (; k < vec.size(); ++k)	{
		if (vec[k] > minHeap.top())//O(log_2_10) 常量时间
		{
			minHeap.pop();
			minHeap.push(vec[k]);
		}
	}

	//打印结果  这个是找前K个,如果是找第K个,那么只打印堆顶元素即可
	while (!minHeap.empty()){
		cout << minHeap.top() << " ";
		minHeap.pop();
	}

	return 0;
}

二、快排分割

经过快排分割函数,能够在 O ( l o g 2 n ) O(log_2n) O(log2n)时间内,把小于基准数的数调整到左边,把大于基准数的数调整到右边,基准数(下标为index)就可以认为是第index+1小的整数了,区间[0,index]内的数就是前index+1小的整数

快排分割比优先级队列效率更高
在这里插入图片描述

int partition(vector<int>& vec, int l, int r) {
	int val = nums[l];
    while(l < r){
        while(l < r && nums[r] > val){
            r--;
        }
        if(l < r){
            nums[l] = nums[r];
            l++;
        }
        
        while(l < r && nums[l] < val){
            l++;
        }
        if(l < r){
            nums[r] = nums[l];
            r--;
        }
    }
	// 这里l == r,循环退出
	vec[l] = val;
	return l;
}

/*
params:
1.vector<int> &arr: 存储元素的容器
2.int i:数据范围的起始下标
3.int j:数据范围的末尾下标
4.int k:需要求的top K
功能描述:通过快排分割函数递归求解第k小的数字,并返回它的值
*/
void selectNoK(vector<int>& vec, int i, int j, int k) {
	int pos = partition(vec, i, j);
	if (pos == k - 1) {
		return;
	}
	if (pos < k - 1) {
		selectNoK(vec, pos + 1, j, k);
	}
	else {
		// pos > k - 1
		selectNoK(vec, i, pos - 1, k);
	}	
}


int main(){
	srand(time(nullptr));
	vector<int> vec;
	for (int i = 0; i < 20; ++i){
		vec.push_back(rand() % 100);
	}
	int k = 5;
	selectNoK(vec, 0, vec.size() - 1, k);
	
	// 这里输出的是前k小的,vec[k-1]是第k小的
	for (int i = 0; i < k; i++) {
		cout << vec[i] << " ";
	}
	return 0;
}

问题:有一个大文件存放很多整数,内存有限制100M,无法全部加载到内存,求最大的前10个

同样的,我们使用哈希映射的方式整数值 % 小文件数量n = file_index,将这个大文件拆分成n个小文件,然后求出每个小文件的top K,最后将这n个top K再加载到内存求一次top K即可

三、海量数据查重和topK的综合应用

查重:数据是否有重复,以及数据重复的次数
topK:有几亿个数字,求元素的值,前K大/小,第K大/小

1. 题目1:数据的重复次数最大/最小的前K个/第K个

解法:哈希统计(map) + 堆/快排分割

//在一组数字中 ,找出重复次数最多的前10个
int main(){
	//用vec存储要处理的数字
	vector<int> vec;
	for (int i = 0; i < 200000; ++i)
	{
		vec.push_back(rand());
	}

	//统计所有数字的重复次数,key:数字的值,value:数字重复的次数
	unordered_map<int, int> numMap;
	for (int val : vec)
	{
		/*拿val数字在map中查找,如果val不存在,numMap[val]会插入一个[val, 0]
		这么一个返回值,然后++,得到一个[val, 1]这么一组新数据
		如果val存在,numMap[val]刚好返回的是val数字对应的second重复的次数,直接++*/
		numMap[val]++;//数字重复次数的统计 
	}

	//先定义一个小根堆  最后要打印数字及其重复的次数,拿重复的次数进行比较,数字只是为了输出 
	using Pair = pair<int, int>;
	using FUNC = function<bool(Pair&, Pair&)>;
	using MinHeap = priority_queue<Pair, vector<Pair>, FUNC>;
	MinHeap minheap([](Pair& a, Pair& b)->bool {
		return a.second > b.second;//自定义小根堆元素的大小比较方式,比较的是重复的次数 
	});

	//先往堆放10个数据
	int i = 0;
	int k = 10;
	auto it = numMap.begin();

	//先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
	for (; it != numMap.end() && i < k; ++it, ++i)
	{
		minheap.push(*it);
	}

	//把K+1到末尾的元素进行遍历,和堆顶元素比较
	for (; it != numMap.end(); ++it)
	{
		//如果map表中当前元素重复次数 大于 堆顶元素的重复次数,则替换
		if (it->second > minheap.top().second)
		{
			minheap.pop();
			minheap.push(*it);
		}
	}
	//堆中剩下的就是重复次数最大的前k个
	while (!minheap.empty())
	{
		auto& pair = minheap.top();
		cout << pair.first << " : " << pair.second << endl;
		minheap.pop();
	}
	return 0;
}

2. 题目2:有一个大文件,内存限制200M,求文件中重复次数最多的前10个

同样的,我们使用哈希映射的方式整数值 % 小文件数量n = file_index,将这个大文件拆分成n个小文件,然后使用哈希统计 + 小根堆的方式逐个文件读取数据并更新小根堆,最后得到所有小文件重复次数的top K

// 大文件划分小文件(哈希映射)+ 哈希统计 + 小根堆(快排也可以达到同样的时间复杂度)
int main()
{
	srand(time(nullptr));
	FILE* fp_w = fopen("data.dat", "wb");
	for (int i = 0; i < 100000; i++) {
		int data = rand();
		fwrite(&data, sizeof(int), 1, fp_w);
	}
	fclose(fp_w);

	// 打开存储数据的原始文件
	FILE* fp_r = fopen("data.dat", "rb");
	if (fp_r == nullptr)
		return 0;

	// 这里由于原始数据量缩小,所以这里文件划分的个数也变小了,11个小文件
	const int FILE_NO = 11;
	FILE* pfile[FILE_NO] = { nullptr };
	for (int i = 0; i < FILE_NO; ++i)
	{
		char filename[20];
		sprintf(filename, "data%d.dat", i + 1);
		pfile[i] = fopen(filename, "wb+");
	}

	// 哈希映射,把大文件中的数据,映射到各个小文件当中
	int data;
	while (fread(&data, sizeof(int), 1, fp_r) > 0)
	{
		int findex = data % FILE_NO;
		fwrite(&data, sizeof(int), 1, pfile[findex]);
	}
	fclose(fp_r);
	
	// 定义一个链式哈希表
	unordered_map<int, int> numMap;
	// 先定义一个小根堆
	using Pair = pair<int, int>;
	using FUNC = function<bool(Pair&, Pair&)>;
	using MinHeap = priority_queue<Pair, vector<Pair>, FUNC>;
	MinHeap minheap([](auto& a, auto& b)->bool {
		return a.second > b.second; // 自定义小根堆元素大小比较方式
	});

	// 分段求解小文件的top 10大的数字,并求出最终结果
	for (int i = 0; i < FILE_NO; ++i)
	{
		// 恢复小文件的文件指针到起始位置
		fseek(pfile[i], 0, SEEK_SET);

		// unordered_map统计pfile[i]文件中数据重复的次数
		while (fread(&data, sizeof(int), 1, pfile[i]) > 0)
		{
			numMap[data]++;
		}

		int k = 0;
		auto it = numMap.begin();

		// 如果堆是空的,先往堆方10个数据
		if (minheap.empty())
		{
			// 先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
			for (; it != numMap.end() && k < 10; ++it, ++k)
			{
				minheap.push(*it);
			}
		}

		// 把K+1到末尾的元素进行遍历,和堆顶元素比较
		for (; it != numMap.end(); ++it)
		{
			// 如果map表中当前元素重复次数大于,堆顶元素的重复次数,则替换
			if (it->second > minheap.top().second)
			{
				minheap.pop();
				minheap.push(*it);
			}
		}

		// 清空哈希表,进行下一个小文件的数据统计
		numMap.clear();
	}

	// 堆中剩下的就是重复次数最大的前k个
	while (!minheap.empty())
	{
		auto& pair = minheap.top();
		cout << pair.first << " : " << pair.second << endl;
		minheap.pop();
	}

	for (FILE* fp : pfile) {
		fclose(fp);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值