一、位图
现在有一个问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断
一个数是否在这40亿个数中?如果用int存储这40亿个数,太浪费内存了!我们可以考虑只用一
个比特位来标记一个数在不在(因为不重复)。下面的图很好的展示了位图的思想。
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用
来判断某个数据存不存在的。
1.位图的实现
位图的实现涉及到了左移和右移:结合注释并画图,你一定能明白代码的含义😁!
class bitset
{
public:
//N:表示N个比特位(他不是精准的开N比特的空间,可能会比N略大)
//但是N是你需要开的比特位
bitset(size_t N)
{
_bits.resize((N/32)+1, 0);
_num = 0;
}
//把x放进位图中 -> 就是把x映射到的比特位置成1
void set(size_t x)
{
//1.计算x在哪个区间(x映射的位置在第几个整形)
size_t index = x / 32;
//2.计算x在这个整形的第几位
size_t pos = x % 32;
//把pos置成1
_bits[index] |= (1 << pos);
_num++;
}
//把x从位图中删除 -> 就是把x映射到的比特位置成0
void reset(size_t x)
{
//1.计算x在哪个区间(x映射的位置在第几个整形)
size_t index = x / 32;
//2.计算x在这个整形的第几位
size_t pos = x % 32;
//把pos置成0
_bits[index] &= ~(1 << pos);
_num--;
}
//判断x在不在(也就是判断x映射到的位置是否为1)
bool test(size_t x)
{
//1.计算x在哪个区间(x映射的位置在第几个整形)
size_t index = x / 32;
//2.计算x在这个整形的第几位
size_t pos = x % 32;
return _bits[index] & (1 << pos);
}
private:
std::vector<int> _bits;
size_t _num;//表示数据个数
};
2.位图的优缺点
(1)位图的优点:节省空间,效率高。
(2)位图的缺点:只能处理整形,不能处理哈希冲突。
二、布隆过滤器
现在有这样的一个场景:我们在刷抖音时,它会给我们不停地推荐新的内容,所以它每次推荐时
要去重,去掉那些已经看过的内容。问题来了,抖音推荐系统如何实现推送去重的?记录已经给
你推荐过的内容,之后不再给你推荐已经推荐过的内容。表示一个内容已经推荐过,只需要标记
一个位就可以,特点是节省空间。这样会产生哈希冲突:不同的字符串映射到相同的位置上去
了,还没有推荐的内容被误判为已经推荐过了。如何解决上面讲的误判问题?不能解决误判的问
题,只能缓解,也就是降低误判的概率 -> 这就引出了布隆过滤器!
布隆过滤器提出的解决方案:一个值映射一个位置,容易误判,一个值映射多个位置可以降低误
判的概率 -> 这就是布隆过滤器!
下面这幅图很好的展示了一个值映射到多个位置(利用多个哈希函数映射到不同位置)
1.布隆过滤器的实现
布隆过滤器的大框架
//三个哈希函数
struct HashStr1
{
//BKRD
size_t operator()(const std::string& key)
{
size_t hash = 0;
for(size_t i=0; i<key.size(); i++)
{
hash *= 131;
hash += key[i];
}
return hash;
}
};
struct HashStr2
{
//RSHash
size_t operator()(const std::string& key)
{
size_t hash = 0;
size_t magic = 63689;
for(size_t i=0; i<key.size(); i++)
{
hash *= magic;
hash *= key[i];
magic *= 378551;
}
return hash;
}
};
struct HashStr3
{
//SDBM
size_t operator()(const std::string& key)
{
size_t hash = 0;
for(size_t i=0; i<key.size(); i++)
{
hash *= 65599;
hash += key[i];
}
return hash;
}
};
//布隆过滤器
template<class K = std::string,
class Hash1 = HashStr1,
class Hash2 = HashStr2,
class Hash3 = HashStr3>
class bloomfilter
{
public:
bloomfilter(size_t num)
:_bs(5 * num)//经过研究:布隆过滤器的长度是数据量的五倍最好
,_len(5 * num)//所以 位图的有效比特位 就是5*num
{}
void set(const K& key);//插入
bool test(const K& key);//查找
private:
My_bitset::bitset _bs;//位图
size_t _len;//表示 位图中有效比特位的 长度
};
①这幅图很好的展示了布隆过滤器的结构和成员变量的含义(注意分成的两部分)
②三个哈希函数:实现多个映射关系。
(1)布隆过滤器的插入
下图展示了向布隆过滤器中插入:"baidu","tencent"
void set(const K& key)
{
//解释为什么要 % _len
//因为将字符串转化成的整数一般都非常大
//所以要模上_len将 转化的整数 控制在 布隆过滤器的长度范围内
size_t index1 = Hash1()(key) % _len;
_bs.set(index1);
size_t index2 = Hash2()(key) % _len;
_bs.set(index2);
size_t index3 = Hash3()(key) % _len;
_bs.set(index3);
}
(2)布隆过滤器的查找
bool test(const K& key)
{
size_t index1 = Hash1()(key) % _len;
if(_bs.test(index1) == false)
return false;
size_t index2 = Hash2()(key) % _len;
if(_bs.test(index2) == false)
return false;
size_t index3 = Hash3()(key) % _len;
if(_bs.test(index3) == false)
return false;
//但是到这里也不一定是真的在,还可能是误判
return true;
}
①判断不在,是准确的
②判断在,是不准确的:假如A在,那么一定能判断出A在;假如A不在,那么有可能误判成A在
(3)布隆过滤器的删除
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
2.布隆过滤器的优缺点
(1)布隆过滤器的优点:节省空间,高效,可以标记存储任意类型。
(2)布隆过滤器的缺点:存在误判,不支持删除。