例如:50
亿int
整型数,以及一台可用内存为400M
的机器,时间复杂度要求O(n)
,统计只出现一次的数。
需要一种能够在满足 O(n) 时间复杂度要求的同时,使用尽可能少的内存来解决问题。
在处理海量数据时,分治思想是一种常用的策略。它的基本思想是将一个大问题分解成许多小问题,然后逐个解决这些小问题,最后将结果合并起来得到原始问题的解。这种方法可以有效地降低问题的复杂度,并使得问题更容易处理。
分治思想在处理海量数据时通常会按照以下步骤进行:
-
数据分割: 将海量数据划分成多个较小的数据块。这可以根据数据的特点来进行划分,比如按照数据范围、数据哈希值、数据的某种属性等。
-
并行处理: 将每个数据块分配给不同的处理单元或线程进行处理。这样可以利用并行处理的优势,提高处理效率。
-
局部处理: 在每个数据块上应用分治思想,将问题进一步分解成小问题,并在局部范围内解决。
-
结果合并: 将每个数据块上的处理结果合并起来,得到原始问题的解。
分治思想的优点在于可以将大问题分解成小问题,并且可以充分利用并行处理的优势,提高处理效率。但是在实际应用中,需要注意数据块的划分方式、合并策略以及并行处理的管理等问题,以确保整个处理过程的正确性和效率。
分析:
50亿 5G*4(4字节)=20G*2 = 40G(哈希表还得*2)
分治法:大文件划分成小文件,使得每一个小文件能够加载到内存中,
求出对应的重复元素,把结果写入到一个存储重复元素的文件中
大文件-》小文件个数(40G/400M=120个小文件)
遍历大文件的元素,把每一个元素根据哈希映射函数,放到对应序号的小文件当中
data % 127 = file_index
值相同的,通过一样的哈希映射函数,肯定是放在同一个小文件中的
int main()
{
//.dat格式高效存储二进制数据
FILE *pf1 = fopen("data.dat","wb");
for (int i = 0;i < 200000;++i)
{
int data = rand();
fwrite(&data,4,1,pf1);
}
fclose(pf1);
//打开存储数据的原始文件data.dat
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]);
}
//定义一个链式哈希表
unordered_map<int,int> numMap;
//先定义一个小根堆
using P = pair<int,int>;
using FUNC = function<bool(P&,P&)>;
using MinHeap = priority_queue<P,vector<P>,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);
//这里直接统计了数字重复的次数
while (fread(&data,4,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();
}
#if 0
//用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)
{
numMap[val]++;
}
//先定义一个小根堆 数字-》重复次数
using P = pair<int,int>;
using FUNC = function<bool(P&,P&)>;
using MinHeap = priority_queue<P,vector<P>,FUNC>;
//自定义小根堆元素的大小比较方式
MinHeap minheap([](auto &a,auto &b)->bool{return a.second>b.second;});
//先往堆放k个数据。
int k = 0;
auto it = numMap.begin();
//先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
for(;it!=numMap.end()&&k<10;++it,++k)
{
minheap.push(*it);
}
// 把K+1到末尾的元素进行遍历,和堆顶元素比较
for(;it!=numMap.end();++it)
{
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();
}
#endif
return 0;
}