【数据结构取经之路】布隆过滤器BloomFilter原理、误判率推导、代码实现

目录

背景介绍

简介

布隆过滤器的实现思路

布隆过滤器的作用

布隆过滤器误判率推导过程

布隆过滤器的实现

布隆过滤器的删除问题

布隆过滤器的优缺点

布隆过滤器的应用


背景介绍

在一些场景下面,有大量数据需要判断是否存在,而这些数据不是整形,导致位图就派不上用场。这时,时代无比呼唤一种新的解决方案,布隆过滤器也就应运而生了。

简介

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好得多,缺点是有一定的误判率和删除困难。

布隆过滤器的实现思路

BloomFilter的实现思路就是把key通过哈希函数转换成整型后在映射一个二进制位。也就是说,BloomFilter = bitset(位图) + Hash函数。考虑到只映射一个位的话哈希冲突的概率较大,所以可以通过几个哈希函数转换出几个整型,然后映射多个二进制位,降低冲突率。

布隆过滤器的作用

布隆过滤器可以告诉我们**"某样东西一定不存在或者可能存在"。换句话说,它判断一个值key在是不准确的,但是判断一个值key不在是准确的。**下面对这句话做出解释。

判断一个值在是不准确的。原因在于布隆过滤器存在误判,也就是说不同的key映射的3个位置上都恰好与其他元素冲突,而且这些位置都被置为了1,返回结果就是在------这就是误判。

判断一个值不在是准确的。首先,导致返回结果为不存在有两种情况,第一:元素key本来就存在,但是由于误判,导致返回结果为不存在。第二:元素key本来就不存在,然后返回结果为不存在。针对第一种情况,因为布隆过滤器保证元素不被错误的删除,元素存在的话它映射的3个二进制位一定为1,所以这种情况不可能发生。只能是第二种情况,即只有不存在的值返回结果才是不存在,证明了判断一个值不在是准确的。

布隆过滤器误判率推导过程

数据量:n

误判率:p

bit数组的大小:m

哈希函数的个数:k

针对某个二进制位:

经过1次哈希函数映射后,不被置为1的概率:

经过k个哈希函数映射后该bit位仍未被置为1的概率:(某一个二进制位被置为1后,后面还是有可能会再次映射到该位置,故总的二进制位数还是m)

该二进制位在插入n个值后依旧未被置为1的概率:

则某个二进制位在插入n个值后被置为1的概率:

故误判的概率: ①

根据

①式可化为: ②

令,则②式可化为:

误判率为k的函数,有

两边同时取对数,有

两边同时求导,有 ③

当取最值时,(最值点的导数为0,除了端点),则③式可化为: ④

下面对④式进行化简:

(两边同时乘)

(移项)

(把-k移到lnb内)

观察等式两边的形式,可以得到 ⑤

从⑤式中,推出⑥

在⑥式中,把b换成,有

两边同时取对数,推出最佳的哈希函数个数

根据上述描述,误判概率也可写成⑦

把⑥式代入⑦式,有

上面已推出**,** 则****

两边同时取对数,有****

可化简为****

进一步化简****

进而推出bit数组的大小

由误判率公式可知,在k⼀定的情况下,当n增加时,误判率增加,m增加时,误判率减少。

布隆过滤器的实现

经过上述大篇幅的推导,终于得于推出BloomFilter的误判率,接下来我们着手实现。

各种字符串Hash函数------这是一篇关于各种字符串Hash函数分析的博客,我们选出3个效率较好的来作为我们布隆过滤器的Hash函数。前面在BloomFilter的实现思路中提到,BloomFilter = bitset(位图) + Hash函数,我们的BloomFilter将基于标准库里的位图(当然,你也可以基于自己实现的位图)。

cpp 复制代码
#pragma once
#include <bitset>
#include <string>
#include <iostream>

//字符串Hash函数
struct HashFuncBKDR
{
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash += hash * 31 + ch;
		}
		return hash;
	}
};

//字符串Hash函数
struct HashFuncAP
{
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		for (size_t i = 0; i < str.size(); i++)
		{
			if ((i & 1) == 0)
				hash ^= ((hash << 7) ^ (str[i]) ^ (hash >> 3));
			else
				hash ^= (~((hash << 11) ^ (str[i]) ^ (hash >> 5)));
		}
		return hash;
	}
};

//字符串Hash函数
struct HashFuncDJB
{
	size_t operator()(const std::string& str)
	{
		size_t hash = 5381;
		for (auto ch : str)
		{
			hash = hash * 33 ^ ch;
		}
		return hash;
	}
};

template <size_t N,
	size_t X = 6,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
	class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t hash1 = HashFuncBKDR()(key) % M;
		size_t hash2 = HashFuncAP()(key) % M;
		size_t hash3 = HashFuncDJB()(key) % M;

		//映射多个位
		_bs->set(hash1);
		_bs->set(hash2);
		_bs->set(hash3);
	}

	bool test(const K& key)
	{
		//有一个为0,则不存在
		size_t hash1 = HashFuncBKDR()(key) % M;
		if (_bs->test(hash1) == 0)
			return false;

		size_t hash2 = HashFuncAP()(key) % M;
		if (_bs->test(hash2) == 0)
			return false;

		size_t hash3 = HashFuncDJB()(key) % M;
		if (_bs->test(hash3) == 0)
			return false;

		return true;
	}
private:
	static const size_t M = X * N;
	std::bitset<M>* _bs = new std::bitset<M>;
};

void TestBloomFilter()
{
	BloomFilter<10> bf;
	std::string arr[] = { "百度", "字节", "腾讯" };
	for (auto& str : arr)
	{
		bf.set(str);
	}
	std::cout << bf.test("百度") << std::endl;
	std::cout << bf.test("摆度") << std::endl;
	std::cout << bf.test("摆渡") << std::endl;
}

布隆过滤器的删除问题

先说结论,布隆过滤器默认是不支持删除的。下面我们来分析原因。

请看上图,我们发现,obj1和obj2都映射到了3号位上(哈希冲突),当我们删除obj1时,3号位会被置为0,导致我们再去查找obj2时会找不到,这就相当于间接的把obj2删除了。关于这个问题,有这样一个解决方案:引用计数!一个位置用多个位标记,记录映射这个位的计数值,删除时仅仅减减计数值。我们思考一下,这个方案能完美解决布隆过滤器不好删除的问题吗?这个问题不着急回答,我们先来看看下面的场景。

当我们删除一个值key时,本来key是不在布隆过滤器里的,但由于误判(假设其中冲突的一个位置为上图的4号位),结果认为是key在,然后我们删除它。此时, 4号位的计数值减减,由1减为了0,这样也间接删除了tencent。上述问题的答案就不言而喻了。还有人提出这样一种思路,支持计数方式删除,但是定期重建布隆过滤器。

布隆过滤器的优缺点

优点:

1)空间效率高。

2)查询速度快。

3)保密性强。因为布隆过滤器不存储数据本身。

缺点:

1)存在误判。

2)删除困难。

布隆过滤器的应用

1)爬虫系统中的URL去重

在爬虫系统重,为了避免重复的爬取相同的URL,可以使用布隆过滤器来进行URL的去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL可以直接忽略,避免重复的网络请求和数据处理。

2)垃圾邮件过滤

在垃圾邮件过滤系统中,布隆过滤器可以用来判断邮件是否为垃圾邮件。系统可以将已知的垃圾邮件特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从而提高过滤的效率。

3)预防缓存穿透

在分布式缓存系统中,布隆过滤器可以用来解决缓存穿透的问题。缓存穿透是指恶意用户大量请求不存在的数据,导致请求直接访问数据库,造成数据库压力过大。布隆过滤器可以先判断请求的数据是否在布隆过滤器中,如果不在,直接返回不存在,避免对数据库的无效查询。

4)对数据库查询提效

在数据库中,布隆过滤器可以用来加速查询操作。例如:一个APP要快速判断一个电话号码是否注册过,可以用布隆过滤器来判断一个用户的电话号码是否存在,如果不存在,可以直接返回不存在,避免对数据库的无效查询。如果在,再去数据库进行二次确认。


本文到这就结束啦,感谢支持!

相关推荐
Code_Solitude1 小时前
c语言:关于qsoet函数知识点
c语言·数据结构·算法
音草2 小时前
C++ 继承详解:从概念到菱形继承
c++
weixin_307779132 小时前
C++代码实现MATLAB中的spectralcluster函数功能
开发语言·c++·算法·matlab
Pniubi2 小时前
快速排序(力扣基础)(递归)
java·数据结构·算法
xiaoye-duck2 小时前
《C++ 项目:手写大模型接入 SDK》Trae IDE 开发环境搭建与依赖管理
c++·ide·语言模型
沫璃染墨3 小时前
《Qt从零入门系列(十四):QThread多线程详解——从线程创建到客户端阻塞处理》
开发语言·c++·笔记·qt·ui·交互·线程
草莓熊Lotso3 小时前
【C++ 基于微服务的即时通讯系统】Elasticsearch 从安装到 C++ 客户端二次封装全落地
linux·网络·数据库·c++·elasticsearch·微服务
CHPCWWHSU3 小时前
FreeCAD 源码解读:从 Property 到 Command,看一套 CAD 内核的完整闭环
c++·cad
代钦塔拉3 小时前
【C++/Qt 进阶】设计模式系列(5):适配器模式——给“不兼容的接口“贴一层翻译膜
c++·qt·设计模式
aqiu1111113 小时前
蓝桥杯真题复盘:Fibonacci 数列与黄金分割(C++ 算法详解)
c++·算法·蓝桥杯