海量数据的查重问题主要思路
问题如:哈希表 50亿(5G)个整数(20GB),链式哈希表一个节点有数据域和地址域,故还需额外20GB,总共40GB
- 分治思想
Bloom Filter
:布隆过滤器(判定不存在)- 字符串类型:
TrieTree
字典树(前缀树)
哈希表法
参考代码如下
int main()
{
const int SIZE = 10000;
int arr[SIZE] = { 0 };
for (int i = 0; i < SIZE; ++i)
{
arr[i] = rand() % 500;
}
unordered_map<int, int> map;
for (int val : arr)
{
map[val]++;
}
for (auto pair : map)
{
if (pair.second > 1)
{
cout << "数字:" << pair.first << " 重复次数:" << pair.second << endl;
}
}
return 0;
}
分治思想
场景1
有一个文件,有50亿个整数(或IP地址),内存限制400M,让你找出文件中重复的元素,以及重复次数
分析:
首先直接将文件放到内存肯定不现实,用链式哈希表存50亿个数约为5G*4B = 20GB,再加指针域也是4B,一共约40GB,超出题目要求。所以采用分治的思想:
将大文件分成小文件,使得每个小文件能加入到内存中,求出对应的重复元素,把结果写入到一个存储重复元素的文件中。
大文件=》小文件(40G/400M = 120个 小文件),分别起名为data0.txt,data1.txt,data2.txt … data126.txt
遍历大文件元素,把每个元素根据哈希映射函数,放到对应序号的小文件中:即data % 127 = file_index.
(值相同的通过一样哈希函数,肯定是放在同一个小文件中)
场景2
a,b两个文件,里面都有10亿个整数(如IP地址),内存限制400M,求出两个文件中重复的元素。
分析
10亿个数放在内存就最少要8GB左右空间,完全不合题意,所以采用分治的思路。
10亿文件 = 1G*4 B ,采用链式哈希表则要4G * 2 =8GB空间,划分文件(8G/400M)为27个小文件
a文件全部读到内存中,存储到哈希表;
从b文件读取数据,在内存哈希表中查询
把a和b两个大文件划分成个数相等的一系列小文件:
a0.txt b0.txt
a1.txt b1.txt
a2.txt b2.txt
…
a26.txt b26.txt
从a文件中读数据,通过 data % 27 = file_index
从b文件中读数据,通过 data % 27 = file_index
从a和b两个文件中数据相同的元素,进行哈希映射后必然在相同的文件中,故
a1和b1进行查重,a2和b2进行查重…a26和b26进行查重
Bloom Filter布隆过滤器
在内存有限的情况下,快速判断一个元素是否在一个集合中,可用布隆过滤器。
通俗的来讲,在使用哈希表比较占内存时,它是一种更高级的“位图法”,没有位图法的缺陷(数字少但数字最大值大导致开辟空间大)。
Bloom Filter要点:
Bloom Filter
是由一个位数组+k个哈希函数构成Bloom Filter
空间和时间利用率很高,但它有一定错误率,虽然错误率很低,Bloom Filter
判断某一个元素不在一个集合里,那该元素肯定不在集合中;Bloom Filter
判断某个元素在一个集合里,那该元素可能在,也可能不在。- Bloom Filter查找错误率和位数组大小,以及哈希函数的个数有关系,
- Bloom Filter默认只支持
add
和query
操作,不支持delete
操作(因为存储的状态位可能是其他**数据状态位,**删除导致其他元素查找判断出错) - Bloom Filter增加元素过程:把元素的值通过k个哈希函数进行计算,得到k个值,将这些值对应的位置1
- Bloom Filter查询元素过程:把元素的值通过k个哈希函数进行计算,得到k个值,查看对应位的值是否为1,若有一个为0,表示必然不存在;若都为1,则表示可能存在
- 用Bloom Filter解决上面问题就是:用a文件数据构建Bloom Filter的位数组中的状态值,然后再读取b文件的数据进行Bloom Filter的查询