【数据结构取经之路】布隆过滤器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的概率:1 - \frac{1}{m}

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

该二进制位在插入n个值后依旧未被置为1的概率:(1 - \frac{1}{m})^{kn}

则某个二进制位在插入n个值后被置为1的概率:1 - (1 - \frac{1}{m})^{kn}

故误判的概率:(1 - (1 - \frac{1}{m})^{kn})^{k} ①

根据 lim(1 + \frac{1}{x})^{x}= e(x\rightarrow \Join ) 

①式可化为:(1 - (1 - \frac{1}{m})^{kn})^{k}=  (1 - (1 + \frac{1}{-m})^{-m\cdot \frac{kn}{-m}})^{k}= (1 - e^{-\frac{kn}{m}})^{k}

b= e^{\frac{n}{m}},则②式可化为:(1 - b^{-k})^{k}

误判率为k的函数,有 f(k)= (1 - b^{-k})^{k}

两边同时取对数,有 lnf(k)= kln(1 - b^{-k})

两边同时求导,有 \frac{1}{f(k)}\cdot {f(k)}'= ln(1 - b^{-k})+k\cdot \frac{1}{1-b^{-k}}\cdot b^{-k}\cdot lnb

f(k)取最值时,{f(k)}'= 0(最值点的导数为0,除了端点),则③式可化为: ln(1 - b^{-k})+k\cdot \frac{1}{1-b^{-k}}\cdot b^{-k}\cdot lnb= 0

下面对④式进行化简:

ln(1 - b^{-k})+k\cdot \frac{1}{1-b^{-k}}\cdot b^{-k}\cdot lnb= 0

(1 - b^{-k})ln(1 - b^{-k})+kb^{-k}lnb= 0(两边同时乘(1 - b^{-k})

(1 - b^{-k})ln(1 - b^{-k})= -kb^{-k}lnb(移项)

(1 - b^{-k})ln(1 - b^{-k})= b^{-k}lnb^{-k}(把-k移到lnb内)

观察等式两边的形式,可以得到 1 - b^{-k}= b^{-k}

从⑤式中,推出b^{-k}= \frac{1}{2}

在⑥式中,把b换成e^{\frac{n}{m}},有 e^{-\frac{n}{m}k}= \frac{1}{2}

两边同时取对数,推出最佳的哈希函数个数 k= ln2 \frac{m}{n}

根据上述描述,误判概率也可写成p= (1-b^{-k})^{k}

把⑥式代入⑦式,有p= 2^{-k}

上面已推出k= ln2 \frac{m}{n}p=2^{-ln2\frac{m}{n}}

两边同时取对数,有lnp=ln2^{-ln2\frac{m}{n}}

可化简为lnp={-ln2\frac{m}{n}}ln2

进一步化简lnp={-\frac{m}{n}}(ln2)^{2}

进而推出bit数组的大小m=-\frac{nlnp}{(ln2)^{2}}

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

布隆过滤器的实现

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

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

#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要快速判断一个电话号码是否注册过,可以用布隆过滤器来判断一个用户的电话号码是否存在,如果不存在,可以直接返回不存在,避免对数据库的无效查询。如果在,再去数据库进行二次确认。


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/872709.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

免费分享:2014-2018年全球5.0级及以上地震正式报目录数据集

数据详情 本数据集为2014年—2018年中国台网正式目录&#xff08;统一编目目录&#xff09;全球5.0及以上地震6459次地震数据&#xff0c;属性字段包含发震时刻、经度、纬度、深度、地震类型、震级、参考位置、事件类型等。 数据属性 数据名称&#xff1a;全球5.0级及以上地震…

扑捉一只耿鬼(HTML文件)

图例&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>耿鬼</title><style>body {background: #fff;font-family: Comfortaa, sans-serif;}* {box-sizing:…

【K8s】专题十三:Kubernetes 容器运行时之 Docker 与 Containerd 详解

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 Linux 专栏 | Docker 专栏 | Kubernetes 专栏 往期精彩文章 【Docker】&#xff08;全网首发&#xff09;Kyl…

硬件工程师笔试面试知识器件篇——电容

目录 电容 2.1、基础 电容原理图 电容实物图 2.1.1、定义 2.1.2、原理 2.1.3、电容的类型 分类1: 分类2: 2.1.4、电容的应用 2.2、相关问题 2.2.1、电容器的电容值如何测量 2.2.2、不同类型的电容器在实际应用中有那些具体差异 2.2.3、如何选择合适的电容器来满…

探索Mem0:下一代人工智能与机器学习内存管理基础设施(二)Mem0+Ollama 部署运行

探索Mem0:下一代人工智能与机器学习内存管理基础设施(二) Mem 0(发音为“mem-zero”)通过智能记忆层增强AI助手和代理,实现个性化的AI交互。Mem 0会记住用户偏好,适应个人需求,并随着时间的推移不断改进,使其成为客户支持聊天机器人,AI助手和自治系统的理想选择。 …

HNU-2023电路与电子学-实验1

写在前面&#xff1a; 这是电路与电子学课程的第一次实验&#xff0c;按照指导书的需求在Multisim软件搭建一个电路传感器模型&#xff0c;难度较小&#xff0c;细心完成就没有问题。 小tips&#xff1a;22级实验是采用上传到测试平台来进行功能检测&#xff0c;如果不通过则…

ARCGIS 纸质小班XY坐标转电子要素面(2)

本章用于说明未知坐标系情况下如何正确将XY转要素面 背景说明 现有资料&#xff1a;清除大概位置&#xff0c;纸质小班图&#xff0c;图上有横纵坐标&#xff0c;并已知小班XY拐点坐标&#xff0c;但未知坐标系。需要上图 具体操作 大部分操作同这边文章ARCGIS 纸质小班XY…

rsync搭建全网备份

rsync搭建全网备份 1. 总体概述1.1 目标1.2 简易指导图1.3 涉及工具或命令1.4 环境 2. 实施2.1 配置备份服务器2.2 备份文件准备2.3 整合命令2.4 扩展功能 1. 总体概述 1.1 目标 本次搭建目标&#xff1a; 每天定时把服务器数据备份到备份服务器备份完成后进行校验把过期数据…

OpenCV绘图函数(14)图像上绘制文字的函数putText()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在图像上绘制指定的文本字符串。 cv::putText 函数在图像上绘制指定的文本字符串。无法使用指定字体渲染的符号会被问号&#xff08;?&#xff…

电信500M宽带+AX210无线网卡测速

500M电信宽带&#xff0c;PC的Wifi模块是AX210 一、PC测速 2.4G Wifi 5G Wifi 有线网口 二、 手机端&#xff0c;小翼管家App测速 2.4G Wifi 5G Wifi 结论&#xff1a; 手机上网要快的话&#xff0c;还是要选择5G wifi

Vue 使用接口返回的背景图片和拼图图片进行滑动拼图验证

一、背景 前两天发了一篇 vue-monoplasty-slide-verify 滑动验证码插件使用及踩坑_vue-monoplasty-slide-verify 引用后不显示-CSDN博客 这两天项目又需要通过接口校验&#xff0c;接口返回了背景图片和拼图图片&#xff0c;于是在网上找了一篇帖子&#xff0c;vue 图片滑动…

四、搭建网站服务器超详细步骤——解决宝塔界面无法登录问题

前言 本篇博客是搭建网站服务器的第四期&#xff0c;也到了中间的一节 先分享一下我在搭建网站时的个人感受&#xff0c;我在这个环节卡住了很久 后来突然醒悟了&#xff0c;然后成功进入了宝塔界面 现在就来分享一下&#xff0c;我所遇到的问题 小伙伴们坐好了 …

PostgreSQL技术内幕6:PostgreSQL索引技术

文章目录 0. 简介1.PG索引类型介绍2. PG创建索引说明及索引属性查看2.1 创建说明2.2 查看方式2.2.1 查看PG默认支持的索引及对应的Handler类型2.2.2 查看B树索引属性 3. 索引选择3.1 查看索引情况 4.PG中B-Tree索引原理4.1 页存储结构 5.索引代码分析5.1 不同索引结构解析5.1.1…

day15-Linux的优化_linux15个优化

① UID 当前用户uid信息 [rootoldboy59 ~]# id uid0(root) gid0(root) groups0(root) \\UID 当前用户uid信息※② PATH 存放的是命令的位置/路径 [rootoldboy59 ~]# echo $PATH \\用$符号识别环境变量 /usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bi…

【技巧】Excel检查单元格的值是否在另一列中

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 用到的excel函数 IF(ISNUMBER(MATCH(H2, I2:I10, 0)), H2, "") 注意改上面的“H2、I2、I10”&#xff01; 函数效果 函数解释 检查单元格 …

探索MongoDB的Python之钥:pymongo的魔力

文章目录 探索MongoDB的Python之钥&#xff1a;pymongo的魔力背景&#xff1a;为什么选择pymongo&#xff1f;简介&#xff1a;pymongo是什么&#xff1f;安装&#xff1a;如何将pymongo纳入你的项目&#xff1f;基础用法&#xff1a;五个核心函数介绍1. 连接到MongoDB2. 选择数…

[Deepin] 简单使用 RustDesk 实现远程访问Deepin

本教程假设你学会了看官方文档&#xff0c;且拥有基本的IT常识 本教程仅提供可用的方法&#xff0c;并讲述局限性和更优但更复杂的方法&#xff0c;不是一个手把手教程 目标&#xff1a;实现远程访问Deepin 依托 樱花frpRustDesk的“允许通过ip访问” 概述 在RustDesk打开…

【C++】—— string 模拟实现

【C】—— string模拟实现 0 前言1 string的底层结构2 默认成员函数的实现2.1 构造函数2.1.1 无参构造2.1.2 带参构造2.1.2 合并 2.2 析构函数2.3 拷贝构造函数2.3.1 传统写法2.3.2 现代写法 2.3 赋值重载2.3.1 传统写法2.3.2 现代写法2.3.3 传统写法与现代写法的优劣 3 size、…

Bootstrap 字体图标无法显示问题,<i>标签字体图标无法显示问题

bootstrap fileInput 以及 Bootstrap 字体图标无法显示问题。 今天在用 bootstrap fileInput 插件的时候发现图标无法显示&#xff0c;如下&#xff1a; 查看DOM&#xff0c;发现那些图标是<i>标签做的&#xff1a; 网上的方案 方案1 网上很多人说是我们打乱了boots…

每日OJ_牛客_走迷宫(简单bfs)

目录 牛客_走迷宫&#xff08;简单bfs&#xff09; 解析代码&#xff1a; 牛客_走迷宫&#xff08;简单bfs&#xff09; 走迷宫__牛客网 解析代码&#xff1a; 采用一个二维数组&#xff0c;不断的接受迷宫地图(因为有多个地图)&#xff0c;获取到迷宫地图后&#xff0c;采…