gitbub源码链接https://github.com/duchenlong/Cpp/tree/master/hash_bit/hash_bit
哈希位图
在之前的哈希表中,我们采用一个基本数据类型表示一个数据。就像一个int类型
的数据,它占用了4个字节
表示一个数据,但是当数据量很大的时候.
- 比如说
42亿个数据
类型,他的大小就是4G * 4 = 16G
,这时候我们直接开辟这么大的空间显然是不合理,因为32位系统下虚拟内存一共才4G
。
因为一个字节时8位,那么我们是否可以用一个位来表示一个数据呢?
位图的概念
用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
实现方法
类似于哈希的思想,先列一个框架
template<class T>
class HashBit
{
public:
HashBit(size_t N)
{
_table.resize(N / 32 + 1, 0);// +1 防止出现 33 / 32 = 1,的遗留问题
_num = 0;
}
private:
vector<int> _table;
size_t _num = 0;
};
我们在一开始初始化的时候,就直接把这个位图开辟为需要的大小。这里需要注意,我们使用的数据类型为int
,他是4个字节
,也就是32位
。
那么我们开辟数组的大小就是(N / 32 + 1)
,至于为什么需要 +1
,因为计算机中的除法是那种整除,会把余数舍去,所以+1
就是为了避免舍去的余数而造成的越界访问的问题。
中间我们还需要加入一个增加,删除,判断该数是否存在的函数
- 增加一个数(给表示该数的位置1)
首先我们先确定这个数的位置
size_t idx = num / 32;//确定在数组那一个下标位置
size_t pos = num % 32;//确定偏移量
然后我们就把这个位置置1就行了,采用的是或
的方式
//num 数的位置 置1
void set(size_t num)
{
size_t idx = num / 32;
size_t pos = num % 32;
_table[idx] |= (1 << pos);
}
- 删除一个数据(表示该数的位置
置0
)
同理,我们先找到这个数位,然后把这个位置置1
,采取的方式为与
的方式,把该位置置0
,然后其他位置为全1。
//num 数的位置 置0
void reset(size_t num)
{
size_t idx = num / 32;
size_t pos = num % 32;
//_table[idx] = ((_table[idx] | (1 << pos)) ^ (1 << pos));
_table[idx] &= ~(1 << pos);
}
- 判断这个数字是否存在
判断该标志位是否为1
//num 数是否存在数组中
bool test(size_t num)
{
size_t idx = num / 32;
size_t pos = num % 32;
return _table[idx] & (1 << pos);
}
布隆过滤器
我们在CSDN上看文章的时候,在首页的推荐页面中,基本上是很难看到一篇重复的文章。这在他的底层是需要有一个去重的过程的,那么他的底层会是一个什么结构来去重呢?
- 哈希表存储,就是有点浪费空间
- 哈希位图,不容易处理哈希冲突的问题
- 哈希表与位图结合,布隆过滤器
布隆过滤器的概念
布隆过滤器(Bloom Filter)
是1970年由布隆提出的。它实际上是一个很长的二进制向量
和一系列随机映射函数
。
布隆过滤器可以用于检索一个元素是否在一个集合中。
- 优点是 ,空间效率和查询时间都比一般的算法要好的多
- 缺点是,有一定的误识别率和删除困难
布隆过滤器的实现
因为布隆过滤器的原理就是,利用了多个不同的哈希函数来计算插入位置,插入一个数据的时候,其实等于说是插入了多次。
这样当我们进行判断该元素在不在的时候,只需要分别根据这几个哈希函数计算的位置来判断就可以了,
- 只要有一个位置存在数据,那么该数据可能存在;
- 如果有一个位置没有数据,那么该数据一定不存在
所以说布隆过滤器判断数据是否存在,是一个概率事件。但是当判断出一个数据不在的时候,这个数据一定不在原始数据中。
布隆过滤器的大体框架(这里使用了三个哈希函数,所以一开始开辟空间的时候为开辟三倍的空间)
template<class K = string,class Hash1 = HashStr1,
class Hash2 = HashStr2,class Hash3 = HashStr3>
class bloomFilter
{
public:
bloomFilter(size_t num)
:_bs(num * 3)
{}
private:
HashBit _bs;
};
- 插入元素
同时对这三个哈希函数计算的插入位置,进行插入该元素
void set(const K& key)
{
size_t idx1 = Hash1()(key);
size_t idx2 = Hash2()(key);
size_t idx3 = Hash3()(key);
_bs.set(idx1);
_bs.set(idx2);
_bs.set(idx3);
}
- 判断一个数据是否存在
布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
bool test(const K& key)
{
size_t idx1 = Hash1()(key);
if (_bs.test(idx1) == false)
return false;
size_t idx2 = Hash2()(key);
if (_bs.test(idx2) == false)
return false;
size_t idx3 = Hash3()(key);
if (_bs.test(idx3) == false)
return false;
return true;
}
最后,布隆过滤器是不支持删除操作的,因为在删除一个元素的时候,可能会影响到其他真实存在的元素。
是否会存在这样一种情况,就是有一个数据被布隆过滤器进行了误判,它本身是不存在的。然后对他进行删除的时候,就会删除掉该位置本身真实存在的数据。
所以说删除操作是有缺陷的,不可取。
布隆过滤器的优缺点
优点:
- 增加和查询的时间复杂度为O(K),K为哈希函数的个数
- 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
- 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
- 使用布隆过滤器,可以进行交,并,差集的运算
- 可以处理大数据的一些情况,百亿数据这种
缺点:
- 存在误判,可能会把一些不存在的数据,判断为存在
- 不能获取数据元素本身,只能完成从数据到哈希的映射,不能从哈希的位置到数据进行映射。
- 不支持删除元素