海量数据处理(查重,topk)

两盒软妹~` 提交于 2019-12-08 18:32:06

查重问题

查重:就是在一组海量数据中,查找重复的数据,一般的解题思路就是哈希表

哈希表

名称 特点
unordered_set 单重集合,只存放key,不允许key重复
unordered_multiset 多重集合,只存放key,允许key重复
unordered_map 单重映射表,存放[key, value]键值对,不允许key重复
unordered_multimap 多重映射表,存放[key, value]键值对,允许key重复

示例问题:找第一个重复的数字

int main()
{
	vector<int> vec;//将要查找的数据放在vec中
	for (int i = 0; i < 200000; ++i)
	{
		vec.push_back(rand());
	}
     
	// 用哈希表解决查重,因为只查重,所以用无序集合解决该问题
	unordered_set<int> hashSet;
	for (int val : vec)
	{
		// 在哈希表中查找val
		auto it = hashSet.find(val);
		if (it != hashSet.end())//找到了
		{
			cout << *it << "是第一个重复的数据" << endl;
			return; 
		}
		else
		{
			// 没找到
			hashSet.insert(val);
		}
	}

	return 0;
}

统计数字及其出现的次数可以使用无序映射表

#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
	
	vector<int> vec;
	for (int i = 0; i < 200000; ++i)
	{
		vec.push_back(rand());
	}

	// 用无序映射表统计数字和数字出现的次数
	unordered_map<int, int> hashMap;
	for (int val : vec)
	{
		hashMap[val]++; // 可以直接记录数据并且更新数据出现的次数
	}

	// 打印统计出来的重复的数据
	for (pair<int, int> value : hashMap)
	{
		if (value.second > 1)
		{
			cout << "key:" << value.first << " 重复次数:" << value.second << endl;
		}
	}
	cout << endl;
	return 0;
}

求top k

一、找前top k大的数据用小根堆,找前top k小的数据用大根堆

求vector容器中元素值最大的前10个数字
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
int main()
{
	
	vector<int> vec;
	for (int i = 0; i < 200000; ++i)
	{
		vec.push_back(rand() + i);
	}

	// 定义小根堆
	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())
		{
			minHeap.pop();
			minHeap.push(vec[k]);
		}
	}
	
	// 打印结果
	while (!minHeap.empty())
	{
		cout << minHeap.top() << " ";
		minHeap.pop();
	}
	cout << endl;
	return 0;
}

二、利用快排分割函数

partation函数

int partation(vector<int> &arr, int i, int j)
{
	int k = arr[i];
	while (i < j)
	{
		while (i < j && arr[j] >= k)
			j--;
		if (i < j)
			arr[i++] = arr[j];

		while (i < j && arr[i] < k)
			i++;
		if (i < j)
			arr[j--] = arr[i];
	}
	arr[i] = k;
	return i;
}

int findNoK(vector<int> &arr, int i, int j, int k)
{
	int pos = partation(arr, i, j);
	if (pos == k-1)//找到了
		return arr[pos];
	else if (pos < k-1)//在右边
		return selectNoK(arr, pos + 1, j, k);
	else//在左边
		return selectNoK(arr, i, pos-1, k);
}
int main()
{
	/*
	求vector容器中元素第10小的元素值
	*/
	vector<int> vec;
	for (int i = 0; i < 200000; ++i)
	{
		vec.push_back(rand() + i);
	}
	
	cout << findNoK(vec, 0, vec.size()-1, 10) << endl;
	return 0;
}

对内存有限制的大数据处理

1.首先将所要计算的数据存进一个大文件中
2.由于内存限制,文件中的内容无法一次性加载进内存,需要将文件进行分割
3.将该大文件分割成每一个文件足够装载进内存的多个小文件

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
// 大文件划分小文件(哈希映射)+ 哈希统计 + 小根堆(快排也可以达到同样的时间复杂度)
int main()
{
	// 打开存储数据的原始文件
	FILE *pf = fopen("data.dat", "rb");
	if (pf == 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, 4, 1, pf) > 0)
	{
		int findex = data % FILE_NO;//相同元素必然会在同一个文件中
		fwrite(&data, 4, 1, pfile[findex]);
	}

	// 因为结果要记录数字和重复的次数,所以需要打包一个类类型
	struct Node
	{
		Node(int v, int c) :val(v), count(c) {}
		// 为什么要实现operator>,因为小根堆里面要比较Node对象的大小
		bool operator>(const Node &src)const
		{
			return count > src.count;
		}
		int val; // 表示数字的值
		int count; // 表示数字重复的次数
	};

	// 定义一个链式哈希表
	unordered_map<int, int> numMap;
	// 先定义一个小根堆
	priority_queue<Node, vector<Node>, greater<Node>> minheap;

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

		while (fread(&data, 4, 1, pfile[i]) > 0)
		{
			numMap[data]++;
		}

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

		// 如果堆是空的,先往堆放10个数据
		if (minheap.empty())//其实就是当读第一个文件时才会进入该循环
		{
			// 先从map表中读10个数据到小根堆中,建立具有10个元素的小根堆,最小的元素在堆顶
			for (; it != numMap.end() && k < 10; ++it, ++k)
			{
				minheap.push(Node(it->first, it->second));
			}
		}

		// 把K+1到末尾的元素进行遍历,和堆顶元素比较
		for (; it != numMap.end(); ++it)
		{
			// 如果map表中当前元素重复次数大于,堆顶元素的重复次数,则替换
			if (it->second > minheap.top().count)
			{
				minheap.pop();
				minheap.push(Node(it->first, it->second));
			}
		}
		
		// 清空哈希表,进行下一个小文件的数据统计
		numMap.clear();
	}
 
	// 堆中剩下的就是重复次数最大的前k个
	while (!minheap.empty())
	{
		Node node = minheap.top();
		cout << node.val << " : " << node.count << endl;
		minheap.pop();
	}

	return 0;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!