例如: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.datFILE *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();}
#endifreturn 0;
}