【数据结构】位图与布隆过滤器

embedded/2024/9/24 17:15:52/

目录

前言

位图的概念

经典面试题目

位图的模拟实现

set()

reset()

test()

位图整体代码

位图的应用

位图的优缺点

布隆过滤器

 布隆过滤器的概念

哈希函数的个数与布隆过滤器长度的关系

布隆过滤器的模拟实现

插入

查找

删除

布隆过滤器整体代码


前言

哈希本质是一种映射(绝对映射/相对映射)的思想,哈希思想的应用之一便是位图位图需要对比特位进行操作,因此需要回顾整型数据在内存中的存储与移位运算;

思考:如何将整型数据的第k个比特位设置为1或者0并且不影响其余的比特位 ?

    无论当前机器是大端存储或者小端存储,由于数字1的最低的比特位为1,左移始终向高位移动,因此数字1先首先左移k个比特位,其次和原序列做或运算便设置第k个比特位为1

同理,首先将数字1左移k个比特位,然后按位取反此时数字1所对应的二进制序列只有第k个比特位为0,其余的比特位皆为1,最后和原序列做与运算便可设置第k个比特位为0

位图的概念

位图是一种基于位操作的数据结构,用于表示一组元素的集合信息;它通常是一个仅包含0和1的数组,其中每个元素对应集合中的一个元素;位图中的每个位代表一个元素是否存在于集合中当元素存在时,对应位的值为1;不存在时,对应位的值为0

经典面试题目

40亿个无符号整数,每个大小4个字节,则一共占用160亿字节,而1GB大约为10亿字节,则总共需要大约16GB,多数电脑的内存根本无法存放这些数据,若将数据存放于磁盘,进行外排序与二分查找,若在磁盘上进行,磁盘加载数据的速度缓慢,会导致效率降低,因此便采用位图解决;

内存中表示一个值是否存在的最小单位为比特位,由于数据范围为0 ~ 2^(32)-1,因此开辟2^(32)个比特位的空间,数据的值与存储位置构成绝对映射来标识该值是否存在;

位图的模拟实现

位图的底层结构为数组,采用vector充当底层容器,数组空间的开辟最小以字节为单位,因此既可以开辟整型数组也可以开辟字符型数组;本文采用开辟整型数组;

//非类型模版参数N指定开辟多少比特位的空间
template<size_t N>
class BitSet
{
public://构造函数中需要开辟空间,否则vector大小为0BitSet(){_bits.resize(N / 32 + 1, 0);}private:vector<int> _bits;//开辟整型数组空间
};
  • 非类型模版参数N指定比特位的个数,而构造函数开辟的整型变量的个数,所以需要N/32;
  • 由于N/32的结果不是整数时会取整而抛弃小数部分,所以需要N/32+1,增加1个整型确保比特位足够映射集合中的数值;

set()

set设置x为存在,即将x映射的比特位设置为1;

void set(size_t x)
{//计算x在第i个整型size_t i = x / 32;//计算x在第j个比特位size_t j = x % 32;_bits[i] = _bits[i] | (1 << j);
}

reset()

 reset设置x为不存在,即将x映射的比特位设置为0;

清空位图中指定位的方法如下:

  1. 计算出该位位于第 i 个整型的第 j 个比特位;
  2. 将1左移 j 位然后按位取反最后和第 i 个整数进行与运算;

//将x映射的比特位设置为0
void reset(size_t x)
{//计算x在第i个整型size_t i = x / 32;//计算x在第j个比特位size_t j = x % 32;_bits[i] = _bits[i] & ~(1 << j);
}

test()

检测某个比特位所标识的数值是否存在,存在则为真,否则为假;

获取位图中指定位的状态的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位;
  2. 将1左移 j 位后与第 i 个整数进行与运算;
  3. 若结果非0,则该位所标识的数值存在,否则此比特位所标识的数值不存在;

位图整体代码

template<size_t N>
class BitSet
{
public:BitSet(){_bits.resize(N / 32 + 1, 0);}//将x映射的比特位设置为1void set(size_t x){//计算x在第i个整型size_t i = x / 32;//计算x在第j个比特位size_t j = x % 32;_bits[i] = _bits[i] | (1 << j);}//将x映射的比特位设置为0void reset(size_t x){size_t i = x / 32;size_t j = x % 32;_bits[i] = _bits[i] & ~(1 << j);}//检测数值x是否存在bool test(size_t x){size_t i = x / 32;size_t j = x % 32;return _bits[i] & (1 << j);}
private:vector<int> _bits;
};

位图的应用

位图的一个比特位只有两种状态标识数据是否存在,而统计次数数据可能出现0次、出现1次,出现1次以上具有三种状态,因此需要两个比特位来标识这三种状态;

template<size_t N>
class two_bit_set
{
public:void set(size_t x){// 原先为00,数据x出现后变为01if (_bs1.test(x) == false&& _bs2.test(x) == false){_bs2.set(x);}else if (_bs1.test(x) == false&& _bs2.test(x) == true){//原先为01,数据x出现后变为10_bs1.set(x);_bs2.reset(x);}//一次及以上不做处理}//数值x出现0次返回0,出现1次返回1,出现1次及以上返回2int test(size_t x){if (_bs1.test(x) == false&& _bs2.test(x) == false){return 0;}else if (_bs1.test(x) == false&& _bs2.test(x) == true){return 1;}else{return 2; // 2次及以上}}
private:BitSet<N> _bs1;BitSet<N> _bs2;
};

位图的优缺点

优点:节省空间,效率高

缺点:一般要求数据范围相对集中,否则会导致空间消耗很大,位图只能处理整型数据,若内容编号为字符串,无法处理;

布隆过滤器

对于位图而言,只能处理整型数据,因为数据的数值采用 【直接定址法】计算哈希值几乎不会产生哈希冲突的问题,虽然字符串可以通过不同的哈希函数将字符串转换为整型,但是字符串的组合形式复杂多样,无论通过哪种哈希函数都不可避免地会出现大量哈希冲突;

此处的哈希冲突指不同的字符串被转换为相同的整型,此时便可能产生误判,即某个字符串明明不在数据集合中,却被系统判定为存在,于是诞生了布隆过滤器

  • 位图中存在:不一定真正存在

    如上图中"百度"和"百渡"转换为整型数值相同,那么映射的位置也相同,所以位图中第1234个比特位是1,就可以说"百度"和"百渡"都存在,但实际上是"百度"存在,而"百渡"不存在,于是产生了误判;

  • 位图不存在:必然不存在

    若字符串"字节"转换为整型后与之对应的位图上的比特位为0,则说明"字节"不存在;

 布隆过滤器的概念

布隆过滤器:当一个数据映射到位图中时采用多个哈希函数将其映射到多个比特位,当判断一个数据是否在位图当中时,需要分别根据这些哈希函数计算出对应的比特位,如果根据不同的哈希函数计算的比特位都设置为1则判定为该数据存在,否则判定为该数据不存在;

布隆过滤器使用多个哈希函数进行映射,目的在于【降低哈希冲突的概率】,一个哈希函数产生冲突的概率相对较高,但多个哈希函数同时产生冲突的概率会下降;

布隆过滤器极大的降低了哈希冲突的概率,但是仍然可能会产生误判:

  • 布隆过滤器判断一个数据存在可能是不准确的,因为这个数据对应的比特位可能被其他一个数据或多个数据占用;
  • 布隆过滤器判断一个数据不存在是准确的,因为该数据存在那么该数据对应的比特位都应该已经被设置为1;

哈希函数的个数与布隆过滤器长度的关系

问题是到底该创建多少个比特位的位图(布隆过滤器长度),又应该使用多少个哈希函数来映射一个字符串呢?

选取k=3,即设计3个哈希函数,则m约等于4.5倍n

布隆过滤器的模拟实现

template<size_t N,
class K=string, //数据默认为字符串
class Hash1 = BKDRHash,//三种字符串哈希算法(将字符串转换为整型)
class Hash2 = APHash,
class Hash3 = DJBHash>
class bloomfilter
{
public:private:static const size_t M = 5 * N;//M布隆过滤器长度=5*插入元素的个数//STL库中位图实现为静态数组(即int arr[]),存储在对象中,数据量大时可能会导致栈溢出,所以使用new开辟堆空间避免栈溢出std::bitset<M>* _bs = new std::bitset<M>;
};

选择三种字符串哈希算法,原文链接:https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

//BKDR版本
struct BKDRHash
{size_t operator()(const string& s){size_t value = 0;for (auto ch : s){value = value * 131 + ch;}return value;}
};
//AP版本
struct APHash
{size_t operator()(const string& s){size_t value = 0;for (size_t i = 0; i < s.size(); i++){if ((i & 1) == 0){value ^= ((value << 7) ^ s[i] ^ (value >> 3));}else{value ^= (~((value << 11) ^ s[i] ^ (value >> 5)));}}return value;}
};
//DJB版本
struct DJBHash
{size_t operator()(const string& s){if (s.empty())return 0;size_t value = 5381;for (auto ch : s){value += (value << 5) + ch;}return value;}
};

插入

当元素插入到布隆过滤器时,布隆过滤器需要提供一个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;_bs->set(hash1);_bs->set(hash2);_bs->set(hash3);
}

查找

布隆过滤器需要提供一个test接口,用于检测某个元素是否在布隆过滤器当中;

检测时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后判断位图中映射的三个比特位是否被设置为1;

只要映射的三个比特位当中有一个比特位没有被设置为1则说明该元素一定不存在;

若映射的三个比特位全部被设置为1,则返回true表示该元素存在;

注意:判断存在的情况可能存在误判;

bool test(const K& key)
{//依次判断key对应的三个位是否被设置size_t hash1 = Hash1()(key) % M;if (_bs->test(hash1) == false)return false;//key一定不存在size_t hash2 = Hash2()(key) % M;if (_bs->test(hash2) == false)return false;//key一定不存在size_t hash3 = Hash3()(key) % M;if (_bs->test(hash3) == false)return false;//key一定不存在return true; // 存在误判(有可能3个位都是跟别人冲突的,所以误判)
}

删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素;

"百度"和"字节"映射的比特位都有第4个比特位,删除上图中"字节"元素,如果直接将该元素所对应的二进制比特位置0,则"百度"元素也被删除,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠;

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作;

总结:布隆过滤器最好不要支持删除操作

布隆过滤器整体代码

struct BKDRHash
{size_t operator()(const string& s){size_t value = 0;for (auto ch : s){value = value * 131 + ch;}return value;}
};
struct APHash
{size_t operator()(const string& s){size_t value = 0;for (size_t i = 0; i < s.size(); i++){if ((i & 1) == 0){value ^= ((value << 7) ^ s[i] ^ (value >> 3));}else{value ^= (~((value << 11) ^ s[i] ^ (value >> 5)));}}return value;}
};
struct DJBHash
{size_t operator()(const string& s){if (s.empty())return 0;size_t value = 5381;for (auto ch : s){value += (value << 5) + ch;}return value;}
};
template<size_t N,
class K=string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
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;_bs->set(hash1);_bs->set(hash2);_bs->set(hash3);}bool test(const K& key){size_t hash1 = Hash1()(key) % M;if (_bs->test(hash1) == false)return false;size_t hash2 = Hash2()(key) % M;if (_bs->test(hash2) == false)return false;size_t hash3 = Hash3()(key) % M;if (_bs->test(hash3) == false)return false;return true;}
private:static const size_t M = 5 * N;std::bitset<M>* _bs = new std::bitset<M>;
};

欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~


http://www.ppmy.cn/embedded/26325.html

相关文章

01-DispatchServlet和RequestMapping

SpringMVC启动 server.port8090 spring.mvc.servlet.load-on-startup1Configuration ComponentScan // 引入配置 PropertySource("classpath:application.properties") EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class}) public c…

设计模式(十一):外观模式

设计模式&#xff08;十一&#xff09;&#xff1a;外观模式 1. 外观模式的介绍2. 外观模式的类图3. 外观模式的实现3.1 创建一个接口3.2 创建接口的实现3.3 创建一个外观类3.4 测试 1. 外观模式的介绍 外观模式&#xff08;Facade Pattern&#xff09;属于结构型模式&#xf…

RocketMQ与Kafka深度对比:消息中间件的选择之战

引言 在当今日益复杂和高速的信息化时代&#xff0c;消息中间件已成为构建高可用、高扩展性系统的核心组件之一。它们为分布式系统提供了异步通信机制&#xff0c;允许各个微服务、组件或应用程序之间以可靠和高效的方式进行数据交换。简单来说&#xff0c;消息中间件就像是企…

vscode 检查更新 没有检查更新按钮

vscode 检查更新 没有检查更新按钮 1、问题描述2、问题分析3、解决方法 1、问题描述 今天在使用vscode写markdown文档时&#xff0c;需要粘贴图片到markdown文档中&#xff0c;结果无法粘贴进来&#xff0c;显示如下&#xff1a;只粘贴了image.png这几个字。 2、问题分析 搜索…

外包干了2个月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;17年通过校招进入武汉某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年五一&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试&…

【DevOps入门到精通】导读:探索软件开发与运维的协同之道

目录 DevOps简介 专栏结构概览 第一部分&#xff1a;入门阶段 DevOps概述 核心实践 工具与环境 第二部分&#xff1a;提高阶段 深入CI/CD 自动化测试进阶 监控与日志 第三部分&#xff1a;精通阶段 容器化与微服务架构 DevSecOps 高级监控与优化 第四部分&#…

MAC M1电脑部署Grafana+Prometheus+Node_exporter

一、安装 1、grafana安装 brew install grafana 2、prometheus安装 brew install prometheus 3、node_exporter安装 brew install node_exporter 二、启动 1、grafana启动 brew services start grafana 2、prometheus启动 brew services start prometheus 3、node_exporter启动…

【MySQL精炼宝库】深度解析索引 | 事务

目录 一、索引 1.1 索引(index)概念&#xff1a; 1.2 索引的作用&#xff1a; 1.3 索引的缺点&#xff1a; 1.4 索引的使用场景&#xff1a; 1.5 索引的使用&#xff1a; 1.6 面试题:索引底层的数据结构&#xff08;核心内容&#xff09;&#xff1a; 1.7 索引列查询(主…