数据结构/C++:位图 布隆过滤器

news/2024/5/9 4:48:22/文章来源:https://blog.csdn.net/fsdfafsdsd/article/details/136984877

数据结构/C++:位图 & 布隆过滤器

    • 位图
      • 实现
      • 应用
    • 布隆过滤器
      • 实现
      • 应用


哈希表通过映射关系,实现了O(1)的复杂度来查找数据。相比于其它数据结构,哈希在实践中是一个非常重要的思想,本博客将介绍哈希思想的两大应用,位图与布隆过滤器。


位图

看到以下题目:

给40亿个无序不重复的无符号整数(unsigned int)。如何判断一个数字是否在这40亿个数字之中?

大部分人拿到这道题,也许会想到mapset哈希这样的容器。但是其有40亿个数据,而且是整型,最后估算下来,光是数据就占用了十多个G,何况还要用红黑树,哈希表这样的结构存储下来,这是不现实的。

仔细想想,对于这道题目而言,一个数据只有两种状态:在/不在。如果我们想要标识两种状态,其实只需要一个比特位就够了,0表示不存在,1表示存在。通过哈希的映射思想,我们可以把每一个数据映射到一个比特位中,这就是位图的概念

在STL库中,已经为我们提供了位图bitset,我先简单讲解一下bitset的接口,再给大家实现一个位图。

在这里插入图片描述

bitset中,存在着一个非类型模板参数N,其代表位图中要开多少个比特位。

接口功能
operator[]返回对应位置的引用
count计算所有比特位中1的个数
size返回比特位的个数
test检测某一个位,是1返回true,是0返回false
set把某一个位的值改为1
reset把某一个位的值改为0

实现

基本框架如下:

template<size_t N>
class bitSet
{
public:private:vector<int> _bits;
};

我们把位图做成了一个模板,模板参数N用于传参,代表要开几个位。那么我们要如何开出N个比特位?其实我们可以用一个int类型的数组vector,一个int有32bit,那么我们开出来的元素个数就是N / 32个。但是由于C++的除法会向下取整,所以我们要额外+1,避免开出来的位不够。这样我们就可以写一个构造函数:

template<size_t N>
class bitSet
{
public:bitSet(){_bits.resize(N / 32 + 1, 0);}private:vector<int> _bits;
};

接着我们来实现bitset中最重要的几个接口:

set

set接口的功能是把指定的位改为1。
现在传进来一个整数x,我们要如何定位到它属于vector中哪一个元素的哪一个位呢?
其实也很简单,一个元素有32bit,那么我们让x / 32就可以得到其对应的整数了。至于它在整数的第几位,那就是x % 32

size_t i = x / 32; // vector的第i个元素
size_t j = x % 32; // 第i个元素的第j个比特位

现在我们的任务就是把第i个元素的第j个比特位变成1。我们可以把数字1左移j位,然后让_bits[i]与左移后的值按位或。这样就不会影响到其他位,还能把目标位变为1。

比如把11001100的第4位变为1:

   11001100 //待修改数据00000001 //数字100010000 //数字1左移4位
------------11001100| 00010000 //按位或------------11011100

可以看到,我们确实把11001100的第4位变为1了。

set接口如下:

void set(size_t x)
{assert(x <= N);size_t i = x / 32;size_t j = x % 32;_bits[i] |= (1 << j);
}

reset
reset接口的功能是把指定的位改为0。

通过之前同样的办法,定位到第i个元素的第j位,接下来的任务就是把第i个元素的第j位变为0。想要让一个位变为0,只要让它按位与上0就可以了,但是我们其它的位不能变,要按位与1。也就是说我们要拿到第j位为0,其它位为1的数据。

我们之前通过数字1的左移,可以拿到第j位为1,其他位为0的数据。那么我们直接取反,就可以得到第j位为0,其它位为1的数据了。

代码如下:

void reset(size_t x)
{assert(x <= N);size_t i = x / 32;size_t j = x % 32;_bits[i] &= ~(1 << j);
}

test
test接口的功能是检测指定位的值是0还是1。

我们直接让1左移j位,按位与就行了,代码如下:

bool test(size_t x)
{assert(x <= N);size_t i = x / 32;size_t j = x % 32;return _bits[i] & (1 << j);
}

这就是位图最重要的三个操作了,整体还是非常简单的。至于其他接口,都只是锦上添花的作用,而且实现起来也很简单,这里不做讲解了。

位图在处理大量数据时,有非常明显的优势,其主要功能如下:

  1. 标识一个数据的状态
  2. 以O(1)的复杂度查找一个数据的状态
  3. 排序 + 去重

应用

我们再看到几个题目,来加深对位图的理解:

给两个文件,分别有100亿个整数(unsigned int),我们只有1G内存,如何找到两个文件的交集?

根据估算,一个文件的大小大约就在37G,这是不可能放进内存中直接比较的,因此我们可以考虑位图。因为所有数据都是整数,所以数据范围在0 - 42亿之间,我们要开42亿个位。经过计算,42亿bit,大概也就是0.48GB,对于内存而言,还是很友好的。

我们分别把两个文件的数据分别插入到两个位图中,此时我们就有两个范围是0 - 42亿数的位图了,总共也就是0.96GB,在1G限制范围内。然后我们再遍历两个位图,分别对比每一个位,只要两张位图该位都是1,那就是文件的交集。

一个文件有100亿个整数(int),设计算法找到出现次数不超过2次的所有整数

先前我们通过一个比特位标识了一个数据在与不在,但是此题总数据存在多种状态:不存在存在一个存在两个以上三种状态。按照位图的思想,标识三种状态,至少需要2bit,比如00表示不存在,01表示存在一个,10表示存在两个及以上。这样我们就可以设计算法了:

template<size_t N>
class two_bit_set
{
public:void set(size_t x){//00 -> 01if (_bs1.test(x) == false&& _bs2.test(x) == false){_bs2.set(x);}//01 -> 10else if (_bs1.test(x) == false&& _bs2.test(x) == false){_bs1.set(x);_bs2.reset(x);}//10 -> 不处理}int test(size_t x){if (_bs1.test(x) == false&& _bs2.test(x) == false){return 0;}else if (_bs1.test(x) == false&& _bs2.test(x) == false){return 1;}else{return 2;//出现2次以上}}private:bitset<N> _bs1;bitset<N> _bs2;
};

以上代码中,我们在类中定义了两个位图,两个位图的同一个位用于标识一个数据的不同状态,这样就可以区分数据的情况了。

以此类推,当我们发现一张位图无法标识一个数据的状态数目时,就可以用多张位图组合


布隆过滤器

假设某个游戏公司,在开服第一天因为过于火爆,有大量的玩家同时注册游戏,这给后台游戏服务器造成了大量压力。其中一个问题就是:游戏要求玩家之前不能有重复的名字,但是每次玩家输入一个名字的时候,都要去后台的数据库查询这个名字存不存在。这导致数据库访问非常迟缓,请问要如缓解这个问题?

以上问题在于,每当一个玩家输入一个名称(字符串),都要去数据库查询,看是否存在相同的名字。有没有办法能够快速查询到一个名字是否重复呢?这就不得不提布隆过滤器了。

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

现在我们有一下字符串:

  • "Hello python"
  • "Hello C++"
  • "Hello C#"
  • "Hello Go"
  • "Hello CSDN"

假设我们现在有一个位图,接着我们把每一个字符串映射到位图中,我们是否可以通过位图来判定一个字符串存不存在呢?这是不准确的,因为两个字符串有可能会被映射到同一个位上,这就会导致误差,于是布隆觉得,我们能不能把误差降到非常低呢?

于是布隆过滤器的思想就诞生了:

把一个数据通过三套不同的哈希函数,映射到三个位上

当我们查找数据的时候,只有这个数据上的三个位都为1,才说明这个数据存在。

比如这样:
在这里插入图片描述

图中竖着的长条,是一个位图,我们输入了一个Hello C++字符串,然后通过三种不同的哈希函数,把这个字符串映射到了三个不同的位上。

接着我们再插入一个Hello python
在这里插入图片描述

Hello python也映射到了三个位,而且没有与Hello C++发生重复。但是也有特殊的情况,比如我再插入Hello Go

在这里插入图片描述
可以看到,Hello C++Hello Go有一个位发生了重复,这会不会造成数据的误判呢?答案是不会的,因为这两个字符串的另外两个位不同,只有一个字符串的三个位都存在,才说明这个字符串有可能存在,比如我现在查询Hello CSDN是否在位图中:
在这里插入图片描述
可以看到,Hello CSDN这个字符串,也映射到了三个位,其中有一个位是1,而另外两个位是0只要有一个位对不上,就说明这个字符串一定不存在。因此Hello CSDN不存在在位图中。

接下来我们就来实现一个这样的布隆过滤器:


实现

哈希函数
这里我们需要用到三个字符串 -> 整型的哈希函数,这里我取用了目前经过研究效果比较好的三个算法:BKDRAPDJB

struct HashFuncBKDR
{//BKDRsize_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};struct HashFuncAP
{//APsize_t operator()(const string& s){size_t hash = 0;int i;for (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
{//DJBsize_t operator()(const string& s){register size_t hash = 5381;for (auto ch : s)hash = hash * 33 ^ ch;return hash;}
};

这个算法的内部实现并不重要,我们只需要知道,它们是三套不同的规则,可以把一个字符串映射到三个不同的位上。

基本结构

template<size_t N,class K = string,class Hash1 = HashFuncBKDR,class Hash2 = HashFuncAP,class Hash3 = HashFuncDJB>
class BloomFilter
{
public:private:bitset<5 * N> _bs;
};

布隆过滤器BloomFilter有五个模板参数,N代表要插入的数据个数,K代表要处理的类型,剩下三个是不同的哈希函数,用于映射不同的位。

假设x为哈希函数的个数,m是布隆过滤器的长度,n是插入元素的个数,经过研究发现,三者满足以下关系式时,布隆过滤器的误判率最低:

x = m n ln ⁡ 2 x=\frac{m}{n} \ln 2 x=nmln2

此处,我们的哈希函数x = 3,那么我们的m大约是n4.3倍。因此在哈希函数为3个的情况下,布隆过滤器的长度最好是插入数据个数的4.3倍。此处我们取整数5倍,因此有bitset<5 * N> _bs;

Set接口

想要插入一个数据,其实就是通过三个哈希函数计算出三个映射位置,并把它们设置为1。
代码如下:

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

Test接口

想要检测一个数据是否存在,就是检测出这个数据对应的三个映射位置是否都是1。

代码如下:

bool Test(const K& key)
{size_t hash1 = Hash1()(key) % (5 * N);if (_bs.test(hash1) == false)return false;size_t hash2 = Hash2()(key) % (5 * N);if (_bs.test(hash2) == false)return false;size_t hash3 = Hash3()(key) % (5 * N);if (_bs.test(hash3) == false)return false;return true; // 存在误判
}

布隆过滤器不能轻易地删除一个数据,比如以下情况:

在这里插入图片描述

字符串Hello C++Hello Go有一个位重复了,如果我们贸然删掉字符串Hello Go,那么就会导致Hello C++有一个位丢失了,那么我们不仅查找不到被删除的Hello Go,也查找不到Hello C++了。因此布隆过滤器不支持删除操作。


应用

布隆过滤器有以下特性:

  1. 如果检测到一个数据不存在,那么这个数据一定不存在
  2. 如果检测到一个数据存在,那么这个数据有可能存在

布隆过滤器最大特点就在于可以100%检测一个数据的不存在。那么我们回到最开始的问题:

每当一个玩家输入一个名称(字符串),都要去数据库查询,看是否存在相同的名字。有没有办法能够快速查询到一个名字是否重复呢?

我们可以把所有名字映射到布隆过滤器中,所有玩家输入一个字符串后要经过以下过程:

  1. 检测该字符串在不在布隆过滤器中
  • 如果不存在,说明这个字符串一定不存在,此时直接返回结果,告诉玩家该名称可用
  • 如果存在,说明这个字符串可能存在,此时再到数据库中去查找

布隆过滤器之所以叫做过滤器,就在于它可以过滤掉所有不存在的情况。

不妨想象一下,现在让两个人给自己的游戏账号取一个名字,它们重复的概率有多高呢?其实很低了。如果一个用户输入一个游戏名称,有80%的概率是不重复的,那么布隆过滤器就可以过滤掉80%的访问量,给数据库降低80%的压力。而且布隆过滤器搜索的时间复杂度仅仅为O(1),可见布隆过滤器有多么强大。


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

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

相关文章

鸿蒙HarmonyOS应用开发之使用NAPI接口在主线程中进行模块加载

场景介绍 Node-API中的napi_load_module接口的功能是在主线程中进行模块的加载&#xff0c;当模块加载出来之后&#xff0c;可以使用函数napi_get_property获取模块导出的变量&#xff0c;也可以使用napi_get_named_property获取模块导出的函数&#xff0c;目前支持以下场景&a…

电子招标采购系统源码之从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理

随着市场竞争的加剧和企业规模的扩大&#xff0c;招采管理逐渐成为企业核心竞争力的重要组成部分。为了提高招采工作的效率和质量&#xff0c;我们提出了一种基于电子化平台的解决方案。该方案旨在通过电子化招投标&#xff0c;使得招标采购的质量更高、速度更快&#xff0c;同…

HBase的Python API(happybase)操作

一、Windows下安装Python库&#xff1a;happybase pip install happybase -i https://pypi.tuna.tsinghua.edu.cn/simple 二、 开启HBase的Thrift服务 想要使用Python API连接HBase&#xff0c;需要开启HBase的Thrift服务。所以&#xff0c;在Linux服务器上&#xff0c;执行如…

亮数据——让你的IP走出去,让价值返回来

亮数据——让你的IP走出去&#xff0c;让价值返回来 前言跨境电商最最最大的痛点——让IP走出去超级代理服务器加速网络免费的代理管理软件亮数据解决痛点亮数据优势介绍亮数据浏览器的使用示例总结 前言 当前社会信息的价值是不可想象的&#xff0c;今天在亮数据中看到了个【…

Jenkins常用插件安装及全局配置

Jenkins常用插件安装及全局配置 前言 ​ Jenkins是一个流行的持续集成工具&#xff0c;通过安装适用的插件&#xff0c;可以扩展Jenkins的功能&#xff0c;并与其他工具和系统集成。本文将介绍一些常用的Jenkins插件以及安装和配置的步骤。通过安装和配置这些常用插件&#xf…

HCIP第二次实验

实验拓扑图&#xff1a; 实验要求&#xff1a; 1、R1和R2使用PPP链路直连&#xff0c;R2和R3把2条PPP链路捆绑为PPP MP直连 2、按照图示配置IP地址 3、R2对R1的PPP进行单向chap验证 4、R2和R3的PPP进行双向chap验证 实验思路&#xff1a; 1、 先按照图示给R1、R2、R3配置好…

C++:变量和常量(3)

变量 什么是变量&#xff1a;变量就是一个装东西的盒子 通俗&#xff1a;变量是用于存放数据的容器。我们通过变量名获取数据&#xff0c;甚至数据可以修改 变量的作用&#xff1a;给指定的内存空间起名&#xff0c;后期通过起的名字就可以调用整个内存空间 定义变量的格式 &a…

关于 C/C++ 1Z(17)开源项目 openppp2 协同程式切换工作流

下述为开源项目 openppp2&#xff08;github&#xff09;构建工作在 C/C 17 的 stackful 有栈协同程式的工作流切换示意图&#xff1a; 在 openppp2 之中采用人工手动方式管理协同程式之间的切换&#xff0c;每个中断过程只是保存线程栈信息&#xff08;如寄存器、当前#PC EIP&…

HTTPS:原理、使用方法及安全威胁

文章目录 一、HTTPS技术原理1.1 主要技术原理1.2 HTTPS的工作过程1.2.1 握手阶段1.2.2 数据传输阶段 1.3 CA证书的签发流程1.4 HTTPS的安全性 二、HTTPS使用方法三、HTTPS安全威胁四、总结 HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket …

我的编程之路:从非计算机专业到Java开发工程师的成长之路 | 学习路线 | Java | 零基础 | 学习资源 | 自学

小伙伴们好&#xff0c;我是「 行走的程序喵」&#xff0c;感谢您阅读本文&#xff0c;欢迎三连~ &#x1f63b; 【Java基础】专栏&#xff0c;Java基础知识全面详解&#xff1a;&#x1f449;点击直达 &#x1f431; 【Mybatis框架】专栏&#xff0c;入门到基于XML的配置、以…

STM32G4 TIM1触发ADC转换

STM32G4 TIM1触发ADC转换 &#x1f4cd;相关篇《HAL STM32G4 ADC手动触发采集各种滤波算法实现》&#x1f388;《HAL STM32G4 TIM1 3路PWM互补输出VOFA波形演示》&#x1f4cd;《HAL STM32G4内部运放的使用》 ✨继欧拉电子无刷电机驱动相关视频学习 – STM32G4 FOC开发实战—TI…

jenkins+newman+postman持续集成环境搭建

一、Newman简介 Newman是一款基于Node.js开发的&#xff0c;可以运用postman工具直接从命令运行和测试postman集合 二、Newman应用 环境准备&#xff1a;js/ cnpm或npm配置好环境&#xff0c;执行如下命令 三、安装newman 验证是否安装成功&#xff0c;命令&#xff1a;newm…

Ps:照片滤镜

照片滤镜 Photo Filter命令提供了一种快速且直观的方式来模拟传统摄影中使用的彩色滤镜效果。这一功能不仅适用于色彩校正&#xff0c;还可以用于创意色彩调整&#xff0c;以增加视觉吸引力或传达特定的情绪。 Ps菜单&#xff1a;图像/调整/照片滤镜 Adjustments/Photo Filter …

求职MAX版

sangwu 在校-随时到岗 经验&#xff1a;无 本科 20岁 Java开发工程师 全国可飞 薪资可谈 本人优势 不懂劳动法&#xff0c;加班可住公司。稳定性高&#xff0c;不愿意跳槽&#xff0c;能坚守到公司倒闭。各职位都能胜任&#xff0c;性价比高。…

【蓝桥杯选拔赛真题49】C++收集宝石 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解析

目录 C收集宝石 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C收集宝石 第十四届蓝桥杯青少年创意编程大赛C选拔赛真题 一、题目要求 1、编程实现 聪聪在玩冒险岛游戏&#xff0c;为了…

Android JNI SO库和对应的CPU架构详解

Android JNI SO库和对应的CPU架构详解 文章目录 Android JNI SO库和对应的CPU架构详解一、前言二、Android CPU架构1、Android系统支持的CPU架构2、如查查看手机的CPU架构&#xff08;1&#xff09;Android13 大屏AML厂商的cpu信息&#xff1a;&#xff08;2&#xff09;电脑An…

【Leetcode每日一题】 递归 - 计算布尔二叉树的值(难度⭐⭐)(44)

1. 题目解析 题目链接&#xff1a;2331. 计算布尔二叉树的值 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路概述&#xff1a; 问题解释&#xff1a;我们面对的是一个节点可能含有逻辑运算符&#xff08;AN…

PCL点云处理之M估计样本一致性(MSAC)平面拟合(二百三十六)

PCL点云处理之M估计样本一致性(MSAC)平面拟合(二百三十五六) 一、算法介绍二、使用步骤1.代码2.效果一、算法介绍 写论文当然用RANSAC的优化变种算法MSAC啊,RANSAC太土太LOW了哈哈 MSAC算法(M-estimator Sample Consensus)是RANSAC(Random Sample Consensus)的一种…

git笔记之撤销、回退、reset方面的笔记

git笔记之撤销、回退、reset方面的笔记 code review! 文章目录 git笔记之撤销、回退、reset方面的笔记1.git 已经commit了&#xff0c;还没push&#xff0c;如何撤销到初始状态git reset --soft HEAD~1git reset HEAD~1&#xff08;等同于 git reset --mixed HEAD~1&#xff0…

探索BPMN:业务流程模型与表示法

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…