一.布隆过滤器的概念:
布隆过滤器是由布隆在1970年提出的一种紧凑型的,比较巧妙的概率性数据结构,特点是高效的插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在,他是用了多个哈希函数,将一个数据映射到位图上。
位图的优点是:节约空间,快。
位图的缺点是:
1.要求数据范围相对集中,如果数据范围分散,内存消耗上升;
2.只可以int使用;对于其他类型的数据会产生冲突;其他类型只能通过哈希转化为整形;但是整形是有限的,字符串类型是无限 的;所以出现了大量的冲突;
为解决位图的误判率高和只能int使用的问题出现了布隆过滤器:
一个数据通过多个哈希函数映射到多个位置;如果这几个位置都是1则表示该数据存在;
发现:随着一个数据对应的位置越多,相应的误判率下降了;但不可消除误判;但是哈希位置过多会导致空间消耗过大(即过滤器长度过大);
所以大佬终结一下过滤器长度和插入元素个数关系式
我们可以来估算一下,假设用 3 个哈希函数,即K=3,ln2 的值我们取 0.7,那么 m 和 n 的关系大概是 m = n×k/ln2=4.2n ,也就是过滤器长度应该是插入元素个数的 4 -5倍
💘虽然布隆过滤器还是会出现误判,因为这个数据的比特位被其他数据所占,但是判断一个数据不存在是准确,不存在就是0!
二.布隆过滤器应用
不需要一定准确的场景。比如游戏注册时候的昵称的判重:如果不在那就是不在,没被使用,在的话可能会被误判。
提高查找效率:客户端中查找一个用户的ID与服务器中的是否相同,在增加一层布隆过滤器提高查找效率:如果不在服务器那就是不在,如果返回存在,需要向服务器验证;
三、布隆过滤器实现
这里使用位图作为基础容器;来模拟布隆过滤器来判断某个元素是否存在;
BKDRHash.h
#pragma once
#include"bitset.h"
#include<string>
struct BKDRHash
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto e : key)
{
hash *= 31;
hash += e;
}
return hash;
}
};
struct APHash
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (size_t i = 0; i < key.size(); i++)
{
char ch = key[i];
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& key)
{
size_t hash = 5381;
for (auto ch : key)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
template<size_t N,class K=string ,class HashFunc1= BKDRHash,class HashFunc2= APHash,class HashFunc3= DJBHash>
class BloomFilter
{
public:
void Set(const K& key)
{
size_t hash1 = HashFunc1()(key) % N;//这里使用的方法和位图一样只是使用了不同的部分哈希函数;
size_t hash2 = HashFunc2()(key) % N;
size_t hash3 = HashFunc3()(key) % N;
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
//这里一般不支持删除,删除一个可能会影响其他值;
//非要支持删除,也可以,用多个位标记一个值,存引用计数(就是在每个位图的格子中添加一个空间来记录个数)
bool Test(const K& key)//查看这个元素是否存在
{
size_t hash1 = HashFunc1()(key) % N;
if (_bs.test(hash1) == false)
{
return false;
}
size_t hash2 = HashFunc2()(key) % N;
if (_bs.test(hash2) == false)
{
return false;
}
size_t hash3 = HashFunc3()(key) % N;
if (_bs.test(hash3) == false)
{
return false;
}
return true;
}
private:
bit::bitset<N> _bs;
};
void TestBF1()
{
BloomFilter<100> bf;
bf.Set("猪八戒");
bf.Set("沙悟净");
bf.Set("孙悟空");
bf.Set("二郎神");
cout << bf.Test("猪八戒") << endl;
cout << bf.Test("沙悟净") << endl;
cout << bf.Test("孙悟空") << endl;
cout << bf.Test("二郎神") << endl;
cout << bf.Test("二郎神1") << endl;
cout << bf.Test("二郎神2") << endl;
cout << bf.Test("二郎神 ") << endl;
cout << bf.Test("太白晶星") << endl;
}
bitset.h
#pragma once
#pragma once
#include<vector>
using namespace std;
namespace bit
{
//位图
template<size_t N>
class bitset
{
public:
bitset()
{
//_bits.resize(N/32+1,0);
_bits.resize((N >> 5) + 1, 0);
}
void set(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
_bits[i] |= (1 << j);
}
void reset(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
_bits[i] &= ~(1 << j);
}
bool test(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
return _bits[i] & (1 << j);
}
private:
vector<int> _bits;
};
//template <size_t N>
//class twobitset
//{
//public:
// void set(size_t x)
// {
// //00->01
// //01->10
// if (_bs1.test(x) == false && _bs2.test(x) == false)
// {
// _bs2.set(x);
// }
// else if (_bs1.text(x) == false && _bs2.test(x) == true)
// {
// _bs1.set(x);
// _bs2.reset(x);
// }
// }
// void PrintOnce()
// {
// for (size_t i = 0; i < N; i++)
// {
// if (_bs1.test(i) == false && _bs2.test(i) == true)
// {
// cout << i << endl;
// }
// }
// cout << endl;
// }
//
//private:
// bitset<N> _bs1;
// bitset<N> _bs2;
//};
template<size_t N>
class twobitset
{
public:
void set(size_t x)
{
//00->01
//01->10
//10->11
//11->不变
if (_bs1.test(x) == false && _bs2.test(x) == false)
{
_bs2.set(x);
}
else if (_bs1.test(x) == false && _bs2.test(x) == true)
{
_bs1.set(x);
_bs2.reset(x);
}
else if (_bs1.test(x) == true && _bs2.test(x) == false)
{
_bs1.set(x);
_bs2.set(x);
}
}
void Print()
{
for (size_t i = 0; i < N; i++)
{
if (_bs1.test(i) == false && _bs2.test(i) == true)
{
cout << "1->" << i << endl;
}
else if (_bs1.test(i) == true && _bs2.test(i) == false)
{
cout << "2->" << i << endl;
}
}
cout << endl;
}
private:
bitset<N> _bs1;
bitset<N> _bs2;
};
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"BloomFilter.h"
int main()
{
TestBF1();
return 0;
}
这里实现插入和查找函数;删除函数没有实现;原因:删除一个元素的相应位图的几个位置可能会影响其他元素的存在与否;
如果想要实现删除操作;需要在每个位图的位置上添加一个计数器;
五.真题展示:
5.1 哈希切割
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top
K的IP?如何直接用Linux系统命令实现?
解决方法:
5.2 位图应用
- 给定100亿个整数,设计算法找到只出现一次的整数?
- 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
- 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整 数
解决方法: