文章目录
- 一.位图(bitset)
- 底层实现:
- 二.布隆过滤器(bloomFilter)
- 底层实现:
- 三.哈希切分思想
一.位图(bitset)
- 位图是一种以一个比特位为数据记录单元的哈希表 ,以无符号整数为key值,采用直接定址法(不存在哈希冲突的问题),其哈希映射函数为
- f ( k e y ) = k e y ( k e y 的存在状态由第 k e y 个比特位来记录 ) f(key)=key(key的存在状态由第key个比特位来记录) f(key)=key(key的存在状态由第key个比特位来记录)
- 比特位为1表示该映射位对应的key存在,比特位为0表示该映射位对应的key不存在
- STL中的位图以
vector<char>
为适配容器,采用位运算的方式实现其功能接口
key存在状态的记录:
底层实现:
//Size记录要存放的数据个数上限(非类型模板参数),即至少需要开辟Size个比特位的空间
template<size_t Size>
class bitset
{
public:
bitset()
{
_table.resize((Size / 8) + 1, 0);
}
//将第key个比特位设置为1,表示key存在于集合中
void set(size_t key)
{
//计算第key个比特位位于vector的第几个字节
size_t bytes = key / 8;
//计算第key个比特位位于某字节的第几个个比特位
size_t bits = key % 8;
//通过位运算将第key个比特位设置为1
_table[bytes] |= (1 << bits);
}
//将第key个比特位设置为0,表示将数据key从集合中删除
void reset(size_t key)
{
//计算第key个比特位位于vector的第几个字节
size_t bytes = key / 8;
//计算第key个比特位位于某字节的第几个个比特位
size_t bits = key % 8;
//通过位运算将第key个比特位设置为0
_table[bytes] &= ~(1 << bits);
}
//查询key是否存在于集合中
bool test(size_t key)
{
//计算第key个比特位位于vector的第几个字节
size_t bytes = key / 8;
//计算第key个比特位位于某字节的第几个个比特位
size_t bits = key % 8;
//通过位运算判断第key个比特位是否为1
return _table[bytes] & (1 << bits);
}
private:
std :: vector<char> _table;
};
- 位图只能记录关键字是否存在于集合中,但相比于红黑树和哈希桶,位图具有很高的空间效率和时间效率,非常适合用于处理海量数据:
bitset<-1>
(-1转换成无符号整数) ,这样一个对象只占用512MB左右的内存,而它可以用于记录所有可能存在key值- 实际应用:
- 快速查找某个数据是否在一个集合中
- 数据排序 + 去重
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
- 配合字符串哈希函数,位图可以用于记录字符串在研究集合中的存在状态,但是不同的字符串可能会对应同一个key值,为了降低不同字符串哈希冲突的概率,一个字符串可以用多个不同的字符串哈希函数多次映射到位图上,由这样的方式设计出的位图称为布隆过滤器
二.布隆过滤器(bloomFilter)
- 同一个字符串通过多个不同的字符串哈希函数多次映射到同一张位图上,从而有效地降低了位图中字符串发生哈希冲突的概率
底层实现:
- 通过复用bitset实现:
//字符串哈希映射函数1
struct BKDRHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
//字符串哈希映射函数2
struct APHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (long i = 0; i < s.size(); i++)
{
size_t ch = s[i];
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
//字符串哈希映射函数3
struct DJBHash
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
// Size是最多不同key的个数
template<size_t Size,class Key = string,class Hash1 = BKDRHash,class Hash2 = APHash,class Hash3 = DJBHash>
class BloomFilter
{
public:
void set(const Key& key)
{
size_t len = Size * _factor;
//同一个字符串映射三次
size_t hash1 = Hash1()(key) % len;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % len;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % len;
_bs.set(hash3);
}
bool test(const Key& key)
{
size_t len = Size * _factor;
//只有三个哈希映射都相同才认为关键字是重复的
size_t hash1 = Hash1()(key) % len;
if (!_bs.test(hash1))
{
return false;
}
size_t hash2 = Hash2()(key) % len;
if (!_bs.test(hash2))
{
return false;
}
size_t hash3 = Hash3()(key) % len;
if (!_bs.test(hash3))
{
return false;
}
return true;
}
private:
static const size_t _factor = 6;
//由于一个key要占用三个比特位,因此需要额外开辟_factor倍数的空间
bitset<Size * _factor> _bs;
};
- 布隆过滤器的应用:
- 布隆过滤器不存储元素本身,在某些对数据保密要求比较严格的场合有很大优势
- 在能够承受一定的误判的场景下,布隆过滤器比其他数据结构时间和空间效率更高
- 数据量很大时,布隆过滤器可以表示数据全集,其他数据结构不能(受内存限制)
- 使用同一组哈希函数的布隆过滤器可以进行交、并、差运算
- 游戏中昵称存在判断等重复数据过滤的场景经常使用布隆过滤器
三.哈希切分思想
- 哈希切分思想是一种处理海量数据的思想方法—假如现在有100亿个字符串,计算机仅有1G的内存可供使用,如何设计算法找到出现次数最多的那个字符串呢?
-
首先,对数据集合进行哈希切分,将其切分为N个子文件(N个子文件从0~N-1编号),切分方法是:用字符串哈希函数
Hasn()
得到每个字符串的key值,然后按照如下映射关系将每个字符串分类放到对应编号为i的子文件中: -
i = H a s h ( k e y ) m o d N i =Hash(key)\mod N i=Hash(key)modN
-
由于相同的字符串一定会被分类到相同的子文件中,因此将每个子文件分别加载到内存中用map进行统计即可.(如果某些子文件太大,则可以继续以相同的方式(用不同的字符串哈希函数)进行哈希切分)
-
- 上述哈希切分方法还可以应用于如下的问题:现有文件A和文件B, 它们分别存储着100亿个字符串,计算机只有1G内存可供使用,如何得到两个文件的交集?
-
一个高效的解决方式:将文件A和文件B分别进行哈希切分:
-
由于相同的字符串一定会被分类到编号相同的子文件中,因此将子文件Ai和Bi两两加载到内存中用set找出共同元素即可
-