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

news2024/12/30 3:54:01

目录

前言

位图的概念

经典面试题目

位图的模拟实现

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大小为0
	BitSet()
	{
		_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映射的比特位设置为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);
	}
	//将x映射的比特位设置为0
	void 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出现后变为01
		if (_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次及以上返回2
	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) == 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.coloradmin.cn/o/1636366.html

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

相关文章

nginx缓存清理

背景 昨天打开我的gpt镜像网站&#xff0c;意外发现静态图片资源全都无法获取了 CoCo-AI 一番排查下来&#xff0c;发现是引用的cdn链接失效了 且cdn源是属于七牛云的&#xff0c;且不再维护&#xff0c;于是果断切换到cloudflare export function getEmojiUrl(unified: str…

iBarcoder for Mac:一站式条形码生成软件

在数字化时代&#xff0c;条形码的应用越来越广泛。iBarcoder for Mac作为一款专业的条形码生成软件&#xff0c;为用户提供了一站式的解决方案。无论是零售、出版还是物流等行业&#xff0c;iBarcoder都能轻松应对&#xff0c;助力用户实现高效管理。 iBarcoder for Mac v3.14…

Android4.4真机移植过程笔记(一)

1、RK源码编译 获取内核源码&#xff1a; git clone git172.28.1.172:rk3188_kernel -b xtc_ok1000 内核编译环境&#xff1a; 从172.28.1.132编译服务器的/data1/ZouZhiPing目录下拷贝toolchain.tar.gz&#xff08;交叉编译工具链&#xff09;并解压到与rk3188_kernel同级目…

计算机英文论文常见错误写作习惯2

目录 第一部分 非常长的句子 在一个句子的主要概念的前面&#xff0c;首先说明目的、地点或原因 将表示时间的短语放在句首的倾向 将最重要的主语放在句首&#xff0c;以示强调 ‘In this paper’, ‘in this study’ 第一部分 非常长的句子 由于作者经常直接从中文翻译…

通过ESXi主机和专业工具导出或导入虚拟机

关于导出虚拟机的用户场景 导出ESXi虚拟机是VMware内置功能之一&#xff0c;可用于数据迁移或作为ESXi备份解决方案。通常情况下&#xff0c;您可以将ESXi中的虚拟机导出为OVF模板&#xff0c;该模板可捕获虚拟机或虚拟设备的状态并存储在一个自包含的包中&#xff0c;其中磁盘…

使 Elasticsearch 和 Lucene 成为最佳向量数据库:速度提高 8 倍,效率提高 32 倍

作者&#xff1a;来自 Elastic Mayya Sharipova, Benjamin Trent, Jim Ferenczi Elasticsearch 和 Lucene 成绩单&#xff1a;值得注意的速度和效率投资 我们 Elastic 的使命是将 Apache Lucene 打造成最佳的向量数据库&#xff0c;并继续提升 Elasticsearch 作为搜索和 RAG&a…

启发式搜索算法4 -遗传算法实战:吊死鬼游戏

相关文章: 启发式搜索算法1 – 最佳优先搜索算法 启发式搜索算法2 – A*算法 启发式搜索算法2 – 遗传算法 有一个小游戏叫吊死鬼游戏&#xff08;hangman&#xff09;&#xff0c;在学习英语的时候&#xff0c;大家有可能在课堂上玩过。老师给定一个英文单词&#xff0c;同学们…

Python人脸识别全面教程

目录 第一部分&#xff1a;人脸识别基础 1.1 人脸检测 1.2 人脸识别算法 1.3 深度学习在人脸识别中的应用 1.4 人脸识别库 第二部分&#xff1a;人脸识别高级技术 2.1 特征提取与人脸编码 人脸编码示例 2.2 人脸识别流程 人脸识别流程示例 2.3 多人脸识别与跟踪 多…

LabVIEW航空发动机主轴承试验器数据采集与监测

LabVIEW航空发动机主轴承试验器数据采集与监测 随着航空技术的迅速发展&#xff0c;对航空发动机性能的测试与监测提出了更高的要求。传统的数据采集与监测方法已难以满足当前高精度和高可靠性的需求&#xff0c;特别是在主轴承试验方面。基于LabVIEW的航空发动机主轴承试验器…

翻译《The Old New Thing》 - How do I cover the taskbar with a fullscreen window?

How do I cover the taskbar with a fullscreen window? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050505-04/?p35703 Raymond Chen 2005年5月5日 如何用全屏窗口覆盖任务栏&#xff1f; 很多时候&#xff0c;人们总是想得太多。…

[1688]jsp工资投放管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 工资投放管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0…

【数据结构】为了节省空间,对于特殊矩阵我们可以这样做……

特殊矩阵的压缩存储 导读一、数组与矩阵1.1 数组1.2 数组与线性表1.3 数组的存储结构1.4 矩阵在数组中的存储1.4.1 行优先存储1.4.2 列优先存储 二、特殊矩阵及其压缩存储三、对称矩阵及其存储3.1 方阵与对称矩阵3.2 对称矩阵的存储3.3 压缩存储的手动实现3.3.1 行优先存储3.3.…

虹科Pico汽车示波器 | 免拆诊断案例 | 起动机免拆诊断故障 2 例

电磁开关、换向器烧蚀及炭刷磨损均会导致起动机偶尔不工作&#xff0c;使发动机偶尔无法起动。由于故障是偶发的&#xff0c;且没有故障代码&#xff0c;这往往会让维修人员无从下手&#xff0c;而用Pico示波器测量起动电流&#xff0c;就会让这些“亚健康状态”一目了然。 案例…

一个好用的MQTT客户端软件

软件功能如下&#xff0c;实现的协议版本是 3.1.1 仅实现了常用的 CONNECT , PUBLISH , SUBSCRIBE 及相应的应答报文。支持以 Hex 格式显示接收的原始报文&#xff08;方便初学者学习&#xff09;。支持所有字段的自定义配置。支持保存与加载配置文件。 软件界面如下所示&…

刷代码随想录有感(50):路径总和

题干&#xff1a; 代码; class Solution { public:bool traversal(TreeNode* node, int count){if(node NULL)return false;if(!node -> left && !node -> right && count 0)return true;if(!node -> left && !node -> right &&…

出现 xx has no default (no arg) constructor 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法 1. 问题所示 执行脱敏函数的时候&#xff0c;出现如下问题&#xff1a; Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Class com.example.test.ChineseNameDesensitizatio…

LLM 构建Data Multi-Agents 赋能数据分析平台的实践之③:数据分析之二(大小模型协同)

一、概述 随着新一代信息技术在产业数字化中的应用&#xff0c;产生了大量多源多模态信息以及响应的信息处理模式&#xff0c;数据孤岛、模型林立的问题也随之产生&#xff0c;使得业务系统臃肿、信息处理和决策效率低下&#xff0c;面对复杂任务及应用场景问题求解效率低。针…

触发器的查看和删除

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 如果想查看当前所有的触发器信息&#xff0c;可以使用数据字典 user_triggers&#xff0c;这个数据字典有很多字段可以查看所有触发器的名称、类型、表名、拥有者等信息。 …

【Vue 2.x】学习vue之一基础部分

文章目录 Vue 一基础部分第一章1、git两个分支主分支子分支 使用方法方式1&#xff1a;采用命令的方式操作分支方式2&#xff1a;在idea中使用git的分支 向git远程仓库提交时忽略文件使用git时的一些冲突注意事项 2、Vue问题1&#xff1a;什么是Vue&#xff1f;问题2&#xff1…

华为5700配置

恢复出厂设置&#xff0c;清空配置 1、更改名字 system-view sysname tp-10-50-01-04 2、配置管理接口 int vlan 1 ip add 10.50.1.4 255.255.254.0 quit 2、链路汇聚 interface eth-trunk 1 mode lacp quit 3、绑定端口 interface eth-trunk 1 trunkport gigabitethernet …