问题描述
求数据的重复次数最大/最小的前K个/第K个元素
方法:哈希统计(map) + 堆/快排分割
求10万个数中重复数量最多的前10个数
#include <iostream>
#include <queue>
#include <unordered_map>
#include <functional>
using namespace std;
// 求海量数据中重复出现次数最多的前K个 / 第K个数
int main()
{
// 求vec中出现次数最多的前10个数
vector<int> vec;
for (int i = 0; i < 100000; ++i)
{
vec.push_back(rand() % 100);
}
// 统计数字重复的次数
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; // 出现按次数比较,greater
});
// 初始化一个大小为10的小根堆
int k = 0;
auto it = numMap.begin();
for (; it != numMap.end() && k < 10; ++it, ++k)
{
minHeap.push(*it);
}
// 剩余元素的出现次数依次和堆顶元素比较
// 出现次数大于堆顶元素则出堆原来元素,入堆新元素
for (; it != numMap.end(); ++it)
{
if (it->second > minHeap.top().second)
{
minHeap.pop();
minHeap.push(*it);
}
}
// 堆中元素就是重复次数最多的前K个元素
while (!minHeap.empty())
{
const pair<int, int>& p = minHeap.top();
cout << p.first << " : " << p.second << endl;
minHeap.pop();
}
return 0;
}
大文件存储数据时对上题进行求解
分析
将大文件按内存限制分为小文件;
将大文件里的数据做哈希映射到指定的小文件中;
每个文件依次进行哈希统计+小根堆比较操作,最后小根堆堆顶就是重复次数第K多的元素
执行流程:
-
生成大文件
-
生成小文件并将大文件数据散列到小文件中
-
定义小根堆
-
分段求解小文件的前K大的数字,堆始终不变
-
- 求每个文件中元素的重复次数(unordered_map)
- 堆空的时候才需要建堆(确定堆的固定大小)
- 剩余元素的出现次数依次和堆顶元素比较并进行出入堆操作
- 转a.到下一个小文件进行处理
完整代码
#include <iostream>
#include <queue>
#include <unordered_map>
#include <functional>
using namespace std;
int main()
{
FILE* pf1 = fopen("data.dat", "wb");
// 1.生成大文件
for (int i = 0; i < 20000; ++i)
{
int data = rand();
// 往文件pf1中写入&data指向的数据,每次写入4B,总共写1次
fwrite(&data, 4, 1, pf1);
}
fclose(pf1);
FILE* pf = fopen("data.dat", "rb");
if (pf == nullptr)
return 0;
// 2.生成小文件并将大文件数据散列到小文件中
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;
// 3.定义小根堆
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; // 出现按次数比较
});
// 4.分段求解小文件的前K大的数字,堆始终不变
for (int i = 0; i < FILE_NO; ++i)
{
// 恢复小文件的指针到起始位置
fseek(pfile[i], 0, SEEK_SET);
// 每次从pfile[i]中读取1个4B的数据
// 4.1 求每个文件中元素的重复次数
while (fread(&data, 4, 1, pfile[i]) > 0)
{
numMap[data]++;
}
// 初始化一个大小为10的小根堆
int k = 0;
auto it = numMap.begin();
// 只有堆空时需要建堆
if (minHeap.empty())
{
for (; it != numMap.end() && k < 10; ++it, ++k)
{
minHeap.push(*it);
}
}
// 4.2 剩余元素的出现次数依次和堆顶元素比较
// 出现次数大于堆顶元素则出堆原来元素,入堆新元素
for (; it != numMap.end(); ++it)
{
if (it->second > minHeap.top().second)
{
minHeap.pop();
minHeap.push(*it);
}
}
numMap.clear(); // 清空后进行下一个小文件的重复数据统计
}
// 堆中元素就是重复次数最多的前K个元素
while (!minHeap.empty())
{
const pair<int, int>& p = minHeap.top();
cout << p.first << " : " << p.second << endl;
minHeap.pop();
}
return 0;
}