【数据结构】哈希应用-布隆过滤器

目录

1、布隆过滤器的概念

2、布隆过滤器误判率推导

3、代码实现

[3.1 Set](#3.1 Set)

[3.2 Test](#3.2 Test)

4、布隆过滤器的删除

5、布隆过滤器的应用


1、布隆过滤器的概念

有⼀些场景下⾯,有⼤量数据需要判断是否存在,⽽这些数据不是整形,那么位图就不能使⽤了,使⽤红⿊树/哈希表等内存空间可能不够。这些场景就需要布隆过滤器来解决。

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 ⼀种紧凑型的、⽐较巧妙的概率型数据结构,特点是⾼效地插⼊和查询,可以⽤来告诉你 "某样东西⼀定不存在或者可能存在",它是⽤多个哈希函数,将⼀个数据映射到位图结构中。此种⽅式不仅可以提升查询效率,也可以节省⼤量的内存空间。

布隆过滤器的思路就是把key先映射转成哈希整型值,再映射⼀个位,如果只映射⼀个位的话,冲突率会⽐较多,所以可以通过多个哈希函数映射多个位,降低冲突率

布隆过滤器这⾥跟哈希表不⼀样,它⽆法解决哈希冲突的,因为他压根就不存储这个值,只标记映射的位。它的思路是尽可能降低哈希冲突。判断⼀个值key在是不准确的,判断⼀个值key不在是准确的。

像这幅图中一个数据映射了3个位,只有当3个位都是1时,才认为这个数据是存在的,注意此时仍然有可能是不存在的,即是存在误判的,这里的猪八戒就是存在的。当3个位置中有一个位置是0时,这个数据就是不存在的,这是一定的,这里的孙悟空就是不存在的。

2、布隆过滤器误判率推导

这里是利用数学就行推导

3、代码实现

布隆过滤器底层使用的是上一节的位图,不过这里需要增加仿函数将其他类型映射为整型

struct HashFuncBKDR
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		} 
		return hash;
	}
};
struct HashFuncAP
{
	// 由Arash Partow发明的⼀种hash算法。
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) // 偶数位字符
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else // 奇数位字符
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >>
					5)));
			}
		}
		return hash;
	}
};
struct HashFuncDJB
{
	// 由Daniel J. Bernstein教授发明的⼀种hash算法。
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		} 
		return hash;
	}
};
template<size_t N,
	size_t X = 5,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:

private:
	static const size_t M = N * X;
	cxf::bitset<M> _bs;
};

这里设计3个哈希函数的布隆过滤器。模板参数N是布隆过滤器开的位数,X是误导率推导中的m / n,根据前面的计算,当哈希函数是3个时,m / n是5则误导率最低的,K是数据的类型,默认是string,后面3个参数是仿函数,将string映射为整型,3个仿函数对同一个string映射的结果需要不同。注意:这里的N = n,X = m / n,所以虽然传过来需要开一个N位的布隆过滤器,但是实际上的位图需要开N * X个位的位图

3.1 Set

将映射到的位置全部置为1

void Set(const K& key)
{
	// 计算在不同仿函数下映射到的位置
	size_t hash1 = Hash1()(key) % M;
	size_t hash2 = Hash2()(key) % M;
	size_t hash3 = Hash3()(key) % M;
	// 将映射到的位置设置为1
	_bs.set(hash1);
	_bs.set(hash2);
	_bs.set(hash3);
}

3.2 Test

检查一个值是否在布隆过滤器中。此时需要检查映射到的位置是否全为1,若全为1,在,此时可能误判,若有1个或1个以上位置不为1,则不在,此时没有误判

bool Test(const K& key)
{
	size_t hash1 = Hash1()(key) % M;
	if (!_bs.test(hash1)) // 如果映射到的这个位置是0,就返回false,此时没有误判
		return false;
	size_t hash2 = Hash2()(key) % M;
	if (!_bs.test(hash2)) // 如果映射到的这个位置是0,就返回false,此时没有误判
		return false;
	size_t hash3 = Hash3()(key) % M;
	if (!_bs.test(hash3)) // 如果映射到的这个位置是0,就返回false,此时没有误判
		return false;

	return true; // 映射到的位置全为1,则返回true,此时可能存在误判
}

此时可以就行测试

void TestBloomFilter()
{
	BloomFilter<10> bf;
	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("猪戒八") << endl;
}

结果是1 1 1 0 0 0,是没有问题的

struct HashFuncBKDR
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		} 
		return hash;
	}
};
struct HashFuncAP
{
	// 由Arash Partow发明的⼀种hash算法。
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) // 偶数位字符
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else // 奇数位字符
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >>
					5)));
			}
		}
		return hash;
	}
};
struct HashFuncDJB
{
	// 由Daniel J. Bernstein教授发明的⼀种hash算法。
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		} 
		return hash;
	}
};
template<size_t N,
	size_t X = 5,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		// 计算在不同仿函数下映射到的位置
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;
		// 将映射到的位置设置为1
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}
	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		if (!_bs.test(hash1)) // 如果映射到的这个位置是0,就返回false,此时没有误判
			return false;
		size_t hash2 = Hash2()(key) % M;
		if (!_bs.test(hash2)) // 如果映射到的这个位置是0,就返回false,此时没有误判
			return false;
		size_t hash3 = Hash3()(key) % M;
		if (!_bs.test(hash3)) // 如果映射到的这个位置是0,就返回false,此时没有误判
			return false;

		return true; // 映射到的位置全为1,则返回true,此时可能存在误判
	}
private:
	static const size_t M = N * X;
	cxf::bitset<M> _bs;
};

4、布隆过滤器的删除

布隆过滤器默认情况下是不支持删除的

在上面这幅图中,猪八戒和孙悟空都在布隆过滤器中,如果此时将猪八戒删除,也就是将猪八戒映射到的3个位都置成0,此时孙悟空也被删除了

解决⽅案:可以考虑计数标记的⽅式,⼀个位置用多个位标记,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度⽀持删除。但是这个⽅案也有缺陷,如果⼀个值不在布隆过滤器中(Test返回结果是真,也就是出现了误判),我们去删除,减减了映射位的计数,那么会影响已存在的值,也就是说,⼀个确定存在的值,可能会变成不存在,这⾥就很坑。当然也有⼈提出,我们可以考虑计数⽅式⽀持删除,但是定期重建⼀下布隆过滤器,这样也是⼀种思路。

5、布隆过滤器的应用

首先,我们先来分析一下布隆过滤器的优缺点

优点:效率⾼,节省空间,相⽐位图,可以适⽤于各种类型的标记过滤

缺点:存在误判(在是不准确的,不在是准确的),不好⽀持删除

布隆过滤器的一些应用

爬⾍系统中URL去重:

在爬⾍系统中,为了避免重复爬取相同的URL,可以使⽤布隆过滤器来进⾏URL去重。爬取到的URL可以通过布隆过滤器进⾏判断,已经存在的URL则可以直接忽略,避免重复的⽹络请求和数据处理。
垃圾邮件过滤:

在垃圾邮件过滤系统中,布隆过滤器可以⽤来判断邮件是否是垃圾邮件。系统可以将已知的垃圾邮件的特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从⽽提⾼过滤的效率。
预防缓存穿透:

在分布式缓存系统中,布隆过滤器可以⽤来解决缓存穿透的问题。缓存穿透是指恶意⽤⼾请求⼀个不存在的数据,导致请求直接访问数据库,造成数据库压⼒过⼤。布隆过滤器可以先判断请求的数据是否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的⽆效查询。
对数据库查询提效:

在数据库中,布隆过滤器可以⽤来加速查询操作。例如:⼀个app要快速判断⼀个电话号码是否注册过,可以使⽤布隆过滤器来判断⼀个⽤⼾电话号码是否存在于表中,如果不存在,可以直接返回不存在,避免对数据库进⾏⽆⽤的查询操作。如果在,再去数据库查询进⾏⼆次确认。

相关推荐
CodeClimb10 分钟前
【华为OD-E卷-木板 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
奶香臭豆腐43 分钟前
C++ —— 模板类具体化
开发语言·c++·学习
游是水里的游1 小时前
【算法day19】回溯:分割与子集问题
算法
不想当程序猿_1 小时前
【蓝桥杯每日一题】分糖果——DFS
c++·算法·蓝桥杯·深度优先
cdut_suye1 小时前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
南城花随雪。1 小时前
单片机:实现FFT快速傅里叶变换算法(附带源码)
单片机·嵌入式硬件·算法
dundunmm1 小时前
机器学习之scikit-learn(简称 sklearn)
python·算法·机器学习·scikit-learn·sklearn·分类算法
古希腊掌管学习的神1 小时前
[机器学习]sklearn入门指南(1)
人工智能·python·算法·机器学习·sklearn
波音彬要多做1 小时前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法