[C/C++] -- 海量数据查重

例如:50亿int整型数,以及一台可用内存为400M的机器,时间复杂度要求O(n),统计只出现一次的数。

需要一种能够在满足 O(n) 时间复杂度要求的同时,使用尽可能少的内存来解决问题。

在处理海量数据时,分治思想是一种常用的策略。它的基本思想是将一个大问题分解成许多小问题,然后逐个解决这些小问题,最后将结果合并起来得到原始问题的解。这种方法可以有效地降低问题的复杂度,并使得问题更容易处理。

分治思想在处理海量数据时通常会按照以下步骤进行:

  1. 数据分割: 将海量数据划分成多个较小的数据块。这可以根据数据的特点来进行划分,比如按照数据范围、数据哈希值、数据的某种属性等。

  2. 并行处理: 将每个数据块分配给不同的处理单元或线程进行处理。这样可以利用并行处理的优势,提高处理效率。

  3. 局部处理: 在每个数据块上应用分治思想,将问题进一步分解成小问题,并在局部范围内解决。

  4. 结果合并: 将每个数据块上的处理结果合并起来,得到原始问题的解。

分治思想的优点在于可以将大问题分解成小问题,并且可以充分利用并行处理的优势,提高处理效率。但是在实际应用中,需要注意数据块的划分方式、合并策略以及并行处理的管理等问题,以确保整个处理过程的正确性和效率。

分析:

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值