数据结构——布隆过滤器

目录

布隆过滤器概念

布隆过滤器实现

哈希函数

布隆过滤器类

加入到布隆过滤器 

判断在不在

测试一下

为啥不写删除? 

测试一下误判率


布隆过滤器概念

布隆过滤器也是一种位图结构,它可以快速的判断字符串在不在位图中。它的优点是节省空间。

我们首先要解决的问题就是如何把字符串映射到位图中,比如现在有"凡人修仙传"、"古典音乐入门"、"音乐剧"这三个字符串,我们怎么把这三个字符串映射到位图中呢?我们需要用字符串哈希算法把这三个字符串转成整形,然后%上空间大小,比如"凡人修仙传"映射到52,"古典音乐入门"映射到17,"音乐剧"映射到79,然后把该位置设置为1,如果要查找就判断映射到的位置是0还是1。

但是最大的问题是啥呢?最大的问题是冲突,假设现在来了一个字符串"歌剧魅影",计算过后它映射到的位置是79,此时会发生什么事情?由于"音乐剧"映射位置是79,因此79位置的值是1,此时会判断"歌剧魅影"是在的,这是不是就扯淡了,因此最大的问题是可能存在误判。

也就是说这里的一个问题,判断一个字符串存在是可能出现误判的。因为可能会出现上面的情况,位图中根本没有"歌剧魅影",但是"歌剧魅影"和存在的"音乐剧"映射到了同一个位置,因此我们得出的结果"歌剧魅影"是存在的,但它其实是不存在的。

而判定一个字符串不在是不可能误判的,如果一个字符串不在,它必然就不在,因为它映射到的那个位置为0,那它肯定不存在。

 那我们怎么解决存在出现误判的问题呢?这个问题解决不了,哈希表有冲突是怎么解决的,是用链表挂起来,但是挂不起来啊,这里是位图,怎么挂呢。

有一个叫布隆的人呢,它是怎么想的呢,他觉得虽然我们解决不了这个问题,但是我们可以降低误判率,它是怎么降低误判率的呢。

举个栗子,假设你要和素未谋面的网友线下见面,在此之前你从没有见过他,你应该如何让他在线下快速找到你呢?很简单,提供尽可能多的信息,我身高1米8,是个男的,穿着红色上衣,鞋子是黑色的,穿着牛仔裤,戴了一个鸭舌帽,手里拿着一多红色玫瑰花,此时网友是不是就能根据这些信息让它尽可能的不找错人 ?即使它看到一个带了鸭舌帽,穿着红色上衣但是个女生,也不会把它当做是你,因为你是男的。只要提供的特征越多,它找错人的概率就越少,因为茫茫人海中满足上述所有特征的人几乎特别少,此时可以大大降低它找错人的概率。

但是如果你说你穿着红色上衣,鞋子黑色,它认错人的概率就会大大增加,因为可能有很多人都穿黑色鞋子红色上衣。

回到布隆过滤器,我也提供更多的信息就好了,我把一个字符串用三个哈希算法,映射到3个不同的位置。此时我要判断一个字符串在不在,就要判断3个位置是不是全都是1,只要有一个不是1,它就不在,虽然"歌剧魅影"和"音乐剧"两个位置都冲突了,但是它有一个位置不在,那它就不在,当然有可能3个位置都冲突了,但是这个概率被降低了,我们虽然无法消除误判,但是我们可以降低误判,并且我们可以通过开更多的空间,让误判率尽可能低。

现在不在是不是准确的?不在是准确的,只要一个位置不为0它就不在。

但是在是存在误判的,映射到的3个位置都可能都是别人是一样的,但是这样的概率相比上面的概率是大大降低了的 。

应用场景

布隆过滤器之所以叫过滤器是因为它主要是用来过滤用的,我们来看看它的应用场景。

不需要精确的场景

比如快速判断昵称是否注册过,现在游戏的所有昵称都放在数据库里,我要注册昵称就需要当我输入完毕就要显示这个昵称存在不存在,但是数据库效率是很低的,此时就可以用上布隆过滤器,我把所有的昵称放到布隆过滤器里,你输入一个昵称我就来判断你在不在。

假设昵称不存在,那肯定是不存在的。

假设昵称存在,被人用了,没有误判,你用不了,这没啥问题。

如果存在误判,它不存在,没有被人用过,但是实际上判断是存在的,被人用过,你也用不了这个昵称,有没有问题?没问题呀,用户怎么知道这个昵称是存在的,而且这个误判率是可以控制到5%到10%的。

如果我们要做到精确怎么办?

还是用布隆过滤器,如果显示昵称不在,那就直接返回了。

如果显示昵称被人用过存在的时候,我再去数据库查一遍,然后把数据库的结果返回给你,这样就可以保证是不会出现误判的。

此时的布隆过滤器是不是就可以过滤到大量的不在的昵称,不在的就过滤掉了,不用去数据库里查了。此时的效率就得到了很大的提升。

布隆过滤器实现

哈希函数

我们首先需要3个哈希函数,来实现映射,我们可以从大佬们提供的哈希函数里面选3个,我选了前3个评分比较高的。

下面的3个哈希函数都实现成了仿函数,在布隆过滤器中的模版参数中直接传递给这三个类,然后通过匿名函数使用这三个哈希函数,等会看下面的代码就了解了。

各种字符串Hash函数 - clq - 博客园

struct  BKDRHash
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash = hash * 131 + ch;  }return hash;}
};struct SDBMHash
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash = 65599 * hash + ch;//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;  }return hash;}
};struct RSHash
{size_t operator()(const string& s){size_t hash = 0;size_t magic = 63689;for (auto ch : s){hash = hash * magic + ch;magic *= 378551;}return hash;}
};

布隆过滤器类

布隆过滤器是一个位图,因此类里面一定要有一个位图,幸运的是C++的STL提供了位图类bitset,我们直接包含#include<bitset>即可使用,它的使用也很简单,set函数将一个位置标记为1,test函数判断一个位置在不在。

第一个模版参数是一个非类型模版参数,需要我们传递布隆过滤器的大小,第二个参数默认是string类型,因为本来就是处理字符串的呀,后面Hash1、Hash2、Hash3就是我们上面的三个哈希函数。

template<size_t N,class K = string, class Hash1 = BKDRHash,class Hash2 = SDBMHash,class Hash3 = RSHash>
class BloomFilter
{
public:void Set(const K& key);  //加入到布隆过滤器bool Test(const K& key); //判断在不在private:bitset<N> _bf;
};

加入到布隆过滤器 

我们只需要调用3个仿函数,计算出3个映射的值,然后对其%N,这里N是布隆过滤器的大小,如果不对N取模会溢出的,然后调用bitset的set方法加入到位图中即可。

  void Set(const K& key){size_t hash1 = Hash1()(key) % N;size_t hash2 = Hash2()(key) % N;size_t hash3 = Hash3()(key) % N;_bf.set(hash1);_bf.set(hash2);_bf.set(hash3);}

判断在不在

同样计算出所在的位置,然后使用test判断在不在,如果一个位置不在就直接返回false,三个都存在说明它存在,返回true即可,但这里可能是存在误判的。

    bool Test(const K& key){size_t hash1 = Hash1()(key) % N;// 如果第一个位置不在,说明不存在直接返回if (_bt.test(hash1) == false)return false;size_t hash2 = Hash2()(key) % N;if (_bt.test(hash2) == false)return false;size_t hash3 = Hash3()(key) % N;if (_bt.test(hash3) == false)return false;// 走到这里说明三3个位置都存在,返回真,但存在误判return true;}

测试一下

#include "BloomFilter.h"int main()
{BloomFilter<13> bf;bf.Set("凡人修仙传");bf.Set("古典音乐入门");bf.Set("音乐剧");cout << bf.Test("凡人修仙传") << endl;cout << bf.Test("古典音乐入门") << endl;cout << bf.Test("音乐剧") << endl;cout << bf.Test("歌剧魅影") << endl;return 0;
}

我们可以看一下打印结果。

这个地方我在Set函数里打印了计算出来的哈希值,方便观察,我们可以看到,"凡人修仙传"、"古典音乐入门"、"音乐剧"这三个字符串的哈希值都是有相互冲突的,并且"音乐剧"里面有两个一样的哈希值,但是3个哈希值都一样的概率还是很低的,并且我们Test了"歌剧魅影"可以发现它并不存在。

为啥不写删除? 

我们的代码里是没有写删除的,这是为啥呢?假设我把"音乐剧"删除了,会发生啥情况,删除之后"凡人修仙传"也不在了,因为它们两个有一个哈希值是相同的,你把一个字符串删除了会影响其它的字符串,因此布隆过滤器是绝对不能支持删除的。

如果我们要让误判率尽可能的低,我们可以多搞几个哈希函数,并且布隆过滤器的空间开多一点。

测试一下误判率

下面的代码就是构造了一对相似的字符串,v1和v2里的字符串是相似的,前缀都一样,就是后缀不同,然后将v1里的字符串全都加入布隆过滤器,然后判断v2里的字符在不在布隆过滤器,如果在说明误判,然后计数+1,最后用误判的个数除以字符串的总个数,就可以相似字符串的误判率。

又构造一对不同的字符串,它们完全不同,v1和v3是不同的,v1加入布隆过滤器,判断v3在不在,如果在说明误判,计数+1,最后用误判的个数除以字符串的总个数,就可以不相似字符串的误判率。

#include "BloomFilter.h"
#include <vector>void TestBloomFilter()
{const size_t N = 100000;BloomFilter<N * 4> bf;vector<string> v1;string url = "https://mp.csdn.net/mp_blog/creation/editor?spm=1011.2124.3001.6192";// 构造 N 个字符串for (size_t i = 0; i < N; i++){v1.push_back(url + to_string(i));}// 将v1中的字符串加入布隆过滤器for (auto& str : v1){bf.Set(str);}// v2和v1是相似字符串(前缀一样),但不一样vector<string> v2;for (size_t i = 0; i < N; i++){v2.push_back(url + to_string(9999999 + i));}size_t n2 = 0;// 遍历 v2,看看v2中的字符串在布隆过滤器中在不在for (auto& str : v2){if (bf.Test(str)) // 误判++n2;}cout << "相似字符集误判率: " << (double)n2 / double(N) << endl;// 不相似字符集vector<string> v3;for (size_t i = 0; i < N; i++){string url = "bilibili.com";url += to_string(i);v3.push_back(url);}size_t n3 = 0;for (auto& str : v3){if (bf.Test(str)) // 误判++n3;}cout << "不相似字符集误判率: " << (double)n3 / double(N) << endl;}int main()
{TestBloomFilter();return 0;
}

字符串的个数是N,我们看看当布隆过滤器空间是4倍的N的时候,误判是多少。

我们可以看到,相似字符串和不相似字符串的误判率都是差不多的,一个8%一个7%。

我们再开大一点空间,开6倍的N。

我们可以看到误判率瞬间降低了。

这里想说的是,只要你舍得开空间,误判率一定会下降。

下面我们直接搞20倍的N看看。

可以看到误判率已经降低到百分之0.00几了,但这也意味着消耗的空间是很大的,因此这里我们可以根据具体场景具体分析,看你看重的是误判率还是空间。

当然这里也是有局限性的,因为字符串前缀都是相同的,如果是一堆完全不相关的数据,这样去测可能更准确一点。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++题解(31) 2025顺德一中少科院信息学创新班(四期)考核:U537296 青蛙的距离 题解

&#xff08;本人参与了这次考核&#xff09; 题目背景 有n个池塘&#xff0c;每个池塘中有m只青蛙。这里的青蛙很奇怪&#xff0c;同一个池塘的青蛙喜欢排成一列&#xff0c;并且他们的位置都是有序的。 对于第i列的青蛙&#xff0c;他们分别在第a[i]个位置。 青蛙的距离定义…

软件工程复试专业课-测试

测试 1 软件质量2 黑盒测试2.1 概念2.2 等价划分类 2.3 边值分析2.4 错误推测2.5 因果图 3 白盒测试3.1概念3.2 覆盖标准3.2.1 语句覆盖3.2.2 判断覆盖3.2.3 条件覆盖3.2.4 判定/条件覆盖3.2.5 条件组合覆盖3.2.6 路径覆盖 4 软件测试的四个阶段5 测试工具 1 软件质量 定义&…

物以类聚的Kmeans:数据分群的暴力美学

物以类聚的Kmeans&#xff1a;数据分群的暴力美学 本文用三维可视化解析Kmeans聚类如何将混沌数据划出清晰边界&#xff0c;从算法原理、数学推导、评估指标到Python实战&#xff0c;揭示“无监督学习”的暴力美学与致命陷阱。 聚类算法 文章摘要 &#x1f50d; 《Kmeans聚类…

正式页面开发-登录注册页面

整体路由设计&#xff1a; 登录和注册的切换是切换组件或者是切换内容&#xff08;v-if和 v-else)&#xff0c;因为点击两个之间路径是没有变化的。也就是登录和注册共用同一个路由。登录是独立的一级路由。登录之后进到首页&#xff0c;有三个大模块&#xff1a;文章分类&…

fopen的打开方式

fopen的打开方式 FILE *fopen(const char *filename, const char *mode); filename表示文件名字, mode表示打开的文件方式

Docker 常用指令手册(学习使用)

CentOS Docker 实用指令手册 1. 安装与配置 # CentOS 安装Docker sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io sudo systemctl st…

PostgreSQL中的外键与主键

在 PostgreSQL 中&#xff0c;外键&#xff08;Foreign Key&#xff09; 是一种用于建立表间关联的数据库约束机制&#xff0c;其核心作用与主键&#xff08;Primary Key&#xff09;有显著区别。以下是详细解析&#xff1a; 一、外键的定义与作用 定义 外键是表中的一个或多个…

NFC拉起微信小程序申请URL scheme 汇总

NFC拉起微信小程序&#xff0c;需要在微信小程序开发里边申请 URL scheme &#xff0c;审核通过后才可以使用NFC标签碰一碰拉起微信小程序 有不少人被难住了&#xff0c;从微信小程序开发社区汇总了以下信息&#xff0c;供大家参考 第一&#xff0c;NFC标签打开小程序 https://…

如何使用clip模型进行OOD

使用CLIP模型进行OOD检测&#xff08;Out-of-Distribution Detection) 的核心思路是&#xff1a;利用CLIP的多模态对齐能力&#xff08;图像和文本的联合嵌入空间&#xff09;&#xff0c;通过计算输入样本与已知类别语义的匹配度&#xff0c;判断其是否属于已知分布。 CLIP的…

Word 插入图片会到文字底下解决方案

一、现象描述 正常情况下&#xff0c;我们插入图片都是这样的。 但有时突然会这样&#xff0c;插入的图片陷于文字底部。 二、网上解决方案 网上有教程说&#xff0c;修改图片布局选项&#xff0c;从嵌入型改成上下型环绕。改完之后确实有用&#xff0c;但是需要手动拖动图片…

1. HTTP 数据请求

相关资源&#xff1a; 图片素材&#x1f4ce;图片素材.zip 接口文档 1. HTTP 数据请求 什么是HTTP数据请求&#xff1a; (鸿蒙)应用软件可以通过(鸿蒙)系统内置的 http 模块 和 Axios&#xff0c;通过 HTTP 协议和服务器进行通讯 学习核心Http请求技术: Http模块 - 属于鸿…

【我的 PWN 学习手札】House of Husk

House of Husk House of Husk是利用格式化输出函数如printf、vprintf在打印输出时&#xff0c;会解析格式化字符如%x、%lld从而调用不同的格式化打印方法&#xff08;函数&#xff09;。同时C语言还提供了注册自定义格式化字符的方法。注册自定义格式化字符串输出方法&#xf…

CTF-web: 查看python代码抽象语法树

抽象语法树&#xff08;Abstract Syntax Tree&#xff0c;简称 AST&#xff09;是源代码的树状表示&#xff0c;展示了代码的语法结构。在 Python 中&#xff0c;ast 模块可以帮助你解析和操作 Python 代码的 AST。 1. 使用 ast 模块解析 Python 代码 我们可以使用 ast.parse …

【多模态】Magma多模态AI Agent

1. 前言 微软杨建伟团队&#xff0c;最近在AI Agent方面动作连连&#xff0c;前两天开源了OmniParser V2&#xff0c;2月26日又开源了Magma&#xff0c;OmniParser专注在对GUI的识别解析&#xff0c;而Magma则是基于多模态技术&#xff0c;能够同时应对GUI和物理世界的交互&…

Linux系统Pycharm界面卡死无法显示其他界面

1、使用如下代码查看Pycharm的进程 ps aux | grep pycharm2、使用kill关闭所有pycharm进程 kill -9 <替换为你进程的PID>不确定可以执行如下代码&#xff0c;直接全部关闭&#xff1a; pkill -9 -f pycharm3、如果界面还是存在并且仍然卡死 如果 pycharm 界面仍然显…

QT异步编程之线程池QThreadPool

一、概述 在一个应用程序中&#xff0c;我们需要多次使用线程&#xff0c;也就意味着&#xff0c;我们需要多次创建并销毁线程。而创建线程并销毁线程的过程势必会消耗内存。QThreadPool是Qt框架中用于管理线程池的类。它提供了一种高效的方式来管理和重用线程&#xff0c;从而…

Python爬虫:WebAssembly案例分析与爬取实战

文章目录 一、WebAssembly简介1.1 什么是WebAssembly?1.2 WebAssembly的优势1.3 WebAssembly 的使用场景1.4 WebAssembly 对爬虫的挑战二、WebAssembly案例分析2.1 案例一:在线游戏2.2 案例二:图像处理应用三、Python爬虫实战:处理WebAssembly模块3.1 案例一3.2 案例二四、…

vue3配置端口,比底部vue调试

import { fileURLToPath, URL } from ‘node:url’ import { defineConfig } from ‘vite’ import vue from ‘vitejs/plugin-vue’ import vueJsx from ‘vitejs/plugin-vue-jsx’ // 关闭vue底部调试模式 // import vueDevTools from ‘vite-plugin-vue-devtools’ // htt…

算法仿真平台搭建1-FFMPEG+RtspSever快速搭建一个RTSP服务器

一、前言 本文相关的全部源码和RtspSever库&#xff0c;我已打包上传&#xff0c;欢迎大家免费下载&#xff0c;testRTSPSever。 每一个嵌入式视觉算法工程师&#xff0c;都应该有一套属于自己的算法仿真和测试环境。可以方便地进行视频、图像等素材进行在线导入&#xff0c;可…

大模型WebUI:Gradio全解12——LangChain原理、架构和组件(2)

大模型WebUI:Gradio全解12——LangChain原理、架构和组件(2) 前言12. LangChain原理及agents构建Gradio UI12.2 学习资料12.2.1 学习文档12.2.2 用途示例12.2.3 OpenAI和DeepSeek例程1. OpenAI示例2. DeepSeek例程参考文献前言 本系列文章主要介绍WEB界面工具Gradio。Gradi…