位图和布隆过滤器

news2024/11/19 2:48:22

目录

位图

布隆过滤器


 

位图

假设有1000 万个范围在1~ 1亿的整数。如何快速查找某个整数是否出现在这1000万个整数中?
当然,这个问题仍然可以使用哈希表来解决。不过,针对这个“特殊”问题,我们可以使用一种比较“特殊”的哈希表,就是位图。
我们申请一个大小为1亿、数据类型为布尔类型(true 或者false)的数组,将这1000万个整数作为数组下标对应的数组元素值设置成true。例如,整数5包含在这1000万个整数中,我们就将下标为5的数组元素值设置为true,即array[5]=true。当要查询某个整数K是否在这1000万个整数中的时候,我们只需要将对应的数组值array[K]取出,查看是否等于true。 如果array[K]=true,那么说明这1000万个整数中包含这个整数K;如果array[K]=false,那么说明这1000万个整数中不包含这个整数K。
我们知道,表示true和false这两个布尔值,只需要用一个二进制位(bit)。 二进制位1表示true,二进制位0表示false。但是,在很多高级编程语言中,布尔类型占用1B大小的内存空间。对于位图,有没有更加节省内存的存储方式呢?
实际上,我们可以用一个char类型数据表示一个长度是8的位图8,同理,用char类型的a[n]数组表示长度是n*8的位图。在存取位图中的数据时,我们用数据除8,得到这个数据存储在哪个数组元素中后,用数据与8求余,得到数组存储在这个数组中的哪一个二进制位上。例如,对于26,与8相除得到的结果是3(商的部分),也就是说,数据存储在a[3]这个数组元素上,然后,将26与8求余的结果是2,也就是说,数据存储在a[3]这个数组元素的第2个二进制位上。

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N / 8 + 1, 0);
	}
	void set(size_t x)
	{
		size_t i = x / 8;
		size_t ij = x % 8;
		_bit[i] |= (1 << j);
	}
	void reset(size_t x)
	{
		size_t i = x / 8;
		size_t ij = x % 8;
		_bits[i] &= ~(1 << j);
	}
	//检测某个数在不在
	bool get(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		return _bits[i] & (1 << j);
	}
private:
	vector<char> _bits;
};

因为位图通过数组下标来定位数据,所以访问效率非常高。而且,我们只需要用一个二进制位就能表示一个数字,在数字范围不大的情况下,相对于哈希表,位图这种数据结构非常节省内存。对于在100万个整数中查找数据这个问题,如果我们使用哈希表来存储这1000万个数据,那么大约需要40MB的内存空间。如果我们使用位图来存储这10000 万个数据,因为数字范围为1~1亿,所以只需要1亿个二进制位,也就是说,12MB左右的内存空间就足够了。


给定100万个整数,设计算法找到只出现一次的整数?

使用两个位图,出现0次,两个位图该数上的比特位都为0,出现1次,第一个位图上该数的比特位为0,第二个位图上该数的比特位为1,出现两次以上,两个位图该数上的比特位都为1.

template<size_t N>
class twobitset
{
public:
	void set(size_t x)
	{
		// 00 -> 01
		if (_bs1.test(x) == false
			&& _bs2.test(x) == false)
		{
			_bs2.set(x);
		}
		else if (_bs1.test(x) == false
			&& _bs2.test(x) == true)
		{
			// 01 -> 10
			_bs1.set(x);
			_bs2.reset(x);
		}
		// 10
	}
	//打印出只出现一次的值(第一个位图上该数的比特位为0,第二个位图上该数的比特位为1)
	void Print()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_bs2.test(i))
			{
				cout << i << endl;
			}
		}
	}

public:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

位图的应用

  • 快速查找某个数据是否在一个集合中
  • 排序 + 去重
  • 求两个集合的交集、并集等
  • 操作系统中磁盘块标记

优点:速度快、节省空间;缺点:只能映射整型。 

布隆过滤器

 不过,位图的应用场景有定的局限性, 就是数据所在的范围不能太大。如果数据所在的范围很大、如在1000万个整数中查找数据这个问题,数据范围不是1~1亿,而是1~10亿,那么位图就要占用10亿个二进制位,也就是120MB大小的内存空间,相比哈希表,内存占用不降反增。为了解决内存占用不降反增这个问题,我们对位图进行改进和优化,于是,布隆过滤器就产生了。

还是刚才提到的在1000万个整数中查找数据这个例子,数据个数是1000万,数据的范围变成了1~ 10亿。布隆过滤器的做法:尽管数据范围增大了,但我们仍然使用包含1亿个二进制位的位图。通过哈希函数对数据进行处理,让哈希值落在1~ 1亿这个范围内。例如,我们把哈希函数设计或简单的求余操作:f(x)=x%n其中,x表示数据,n表示位图的大小(这里是1亿)。我们知道,哈希函数存在冲突问题,对于100000001和1这两个数字,经过上面那个求余取模的哈希函数处理之后,最后的哈希值都是1,这就导致我们无法区分BitSet[1]=true表示的是1还是100000001了。

当然,为了降低冲突发生的概率,我们可以设计一个更复杂、更随机的哈希函数。除此之外,还有其他方法吗?我们看一下布隆过滤器的处理方法。既然一个哈希函数可能会存在冲突,那么使用多个哈希函数一起定位一个数据, 是否能降低冲突发生的概率呢?

我们使用K个哈希函数,分别对同一个数据计算哈希值,得到的结果分别记作X1,X2,X3,..,XK。我们把这K个哈希值作为位图的下标,将对应的BitSet[X1],BitSet[X2],BitSet[X3],...,BitSet[YK]都设置成true,也就是说,我们用K个二进制位而非一一个二进制位来表示一个数据是存在的。
当要查询某个数据是否存在时,我们使用同样的K个哈希函数,分别对数据计算哈希值,得到Y1,Y2,Y3,..,YK,这K个值。我们用这K个哈希值作为下标,看对应位图中的数值是否都为true, 如果BitSet[Y1],BitSet[Y2],BitSet[Y3],...,BitSet[YK]都为tue,则说明这个数据存在; 如果其中任意一个不为true,就说明这个数据不存在。如图所示,数据163经过3个哈希函数计算之后,哈希值分别为1、4、6,因此,BitSet数组中下标为1、4、6的元素值设置为1。当要查询数据237是否存在时,将数据237经过3个哈希函数计算之后,哈希值分别为0、4、7,而在BitSet数组中,下标为0、4、7的元素值并非都为1,因此,判定数据237不存在。

实现代码: 

struct BKDRHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}

		return hash;
	}
};

struct APHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (long i = 0; i < s.size(); i++)
		{
			size_t ch = s[i];
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};


struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

// N最多会插入key数据的个数
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 len = N * _X;
		size_t hash1 = Hash1()(key) % len;
		_bs.set(hash1);

		size_t hash2 = Hash2()(key) % len;
		_bs.set(hash2);

		size_t hash3 = Hash3()(key) % len;
		_bs.set(hash3);
	}

	bool test(const K& key)
	{
		size_t len = N * _X;

		size_t hash1 = Hash1()(key) % len;
		if (!_bs.test(hash1))
		{
			return false;
		}

		size_t hash2 = Hash2()(key) % len;
		if (!_bs.test(hash2))
		{
			return false;
		}

		size_t hash3 = Hash3()(key) % len;
		if (!_bs.test(hash3))
		{
			return false;
		}
		return true;
	}
private:
	static const size_t _X = 6;
	bitset<N* _X> _bs;
};

 对于两个不同的数字,经过一个哈希函数处理之后,可能会产生相同的哈希值。但是,经过K个哈希函数处理之后,K个哈希值都相同的概率就非常低了。不过,这种处理方式又带来了新的问题,那就是容易产生误判。如图所示,数据146、196 存储到BitSet数组之后,下标为0、2、3、4、6、7的元素值设置为1。当要查询数据177是否存在时,经过3个哈希函数计算之后,哈希值分别是1、2、7, 尽管数据177不存在,但BitSet数组中下标为1、2、7的元素值都为1,因此,就会误判为数据177存在。

布隆过滤器的误判有一个特点: 只有在判断其存在的情况下,才有可能发生误判,也就是说。判定为存在时有可能并不存在。如果某个数据经过布隆过滤器后判断为不存在,就说明这个数据是真的不存在,这种情况是不会存在误判的。
尽管布隆过滤器会存在误判,但是,这并不影响它发挥大的作用。很多业务场景对误判有一定的容忍度。 例如“爬虫”中的网址链接判重问题,即便一个没有被爬取过的网页,被误判为己经被爬取。对于搜索引擎,也并不是什么大事情,是可以容忍的,毕竟网页太多了,搜索引擎也不可能完全爬取到。而且,只要我们调整哈希函数的个数、位图大小与要存储数据的个数的比例,就可以将这种误判的概率降到非常低。
除此之外,我们还可以利用布隆过滤器在到定数据不存在的情况下不会出现误判的特点,在访问数据库进行数据查询前,先访问布隆过滤器,如果经过布隆过速器后判定数据不特在,就不需要继续访问数据库了,这样就能减少数据库查询操作。

扩展BloomFilter使得它支持删除元素的操作

布隆过滤器不能直接删除,会导致存在的元素误判为找不到,可以用多个比特位表示,删除就让比特位减1。

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

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

相关文章

阿里云手动创建Nginx-Ingress

阿里云相关文档 1、在ACK管理控制台点击如下 应用市场–>筛选(以ack-ingress-nginx-v1为例)–>点击安装–>一键部署–>自己定义集群、命名空间以及ingress名称 1.20以下集群选中ack-ingress-nginx。 1.20及以上集群选中ack-ingress-nginx-v1。 应用市场 筛选…

Linux学习笔记 --- Linux基础命令Part2

2.9 查找命令(which、find&#xff09; 目标&#xff1a;1. 掌握使用which命令查找命令的程序文件 2. 掌握使用find命令查找指定文件 which命令 我们在前面学习的Linux命令&#xff0c;其实它们的本体就是一个个的二进制可执行程序。 和Windows系统中的.exe文件&#x…

六级备考24天|CET-6|翻译技巧3|翻译2020年6月真题红楼梦|逻辑问题|理解背诵|20:50~22:30

目录 一、逻辑重建 例句1 例句2 例句3 二、定语和状语 定语的翻译原则 什么是状语&#xff1f; 状语位置 状语的基本形式 三、主动和被动 四、无主句 五、并列和连动 连动 六、作题步骤 七、红楼梦 PRACTICE ANSWER​ 时态问题 一、逻辑重建 试比较&#xff1a; 1. 下雨了…

c++中文路径中文文件读写

踩了坑&#xff0c;两个地方需要注意&#xff0c;否则就会乱码或无法找到文件 &#xff08;1&#xff09;采用utf-8格式&#xff0c;对cpp文件进行编码&#xff0c;用utf-8的方式对内容尽心高度写&#xff0c;方法是std::setlocale(LC_ALL, ".UTF-8"); &#xff08…

dvwa靶场通关(二)

第二关&#xff1a;Command Injection&#xff08;命令注入&#xff09; 什么是命令注入&#xff1a; 命令注入就是在需要输入数据的地方输入了恶意代码&#xff0c;而且系统并没有对其进行过滤或者其他处理导致恶意代码也被执行&#xff0c;最终导致数据泄露或者正常数据被破…

软件测试基础知识整理(八)- 软件缺陷

目录 一、软件缺陷 1.1 缺陷定义 1.2 缺陷判定标准 1.3 软件缺陷产生的原因 1.4 软件缺陷产生的根源 1.5 软件缺陷信息 1.5.1 缺陷状态 1.5.2 缺陷严重程度 1.5.3 缺陷优先级 1.6 缺陷报告模板 1.7 缺陷报告注意事项 1.8 缺陷跟踪流程 1.9 缺陷数据分析关注的问题 …

chatgpt赋能python:Pythonsearchsorted:用于搜索排序数组的快速工具

Python searchsorted&#xff1a;用于搜索排序数组的快速工具 在Python编程中&#xff0c;有时需要在有序数组中快速查找值的位置。Python searchsorted工具提供了一种快速而高效的方法&#xff0c;可用于在已排序的数组中搜索值的位置。在本文中&#xff0c;将深入探讨Python…

实验二:熟悉常用的HDFS操作

实验环境: (1)操作系统:Linux(建议 Ubuntu 16.04 或 Ubuntu 18.04)。 (2)Hadoop 版本:3.1.3。 (3)JDK 版本:1.8。 (4)Java IDE: Eclipse。 实验内容与完成情况: (1)编程实现以下功能,并利用Hadoop提供的Shell命令完成相同任务。 ①向HDFS中上传任意文本文件,如果指定的文…

简介KettlePack

目录 &#x1f3c6;1、本机环境&#xff1a; &#x1f3c6;2、虚拟机环境&#xff1a; &#x1f3c6;3、安装MySQL &#x1f3c6;4、安装kettlePack ⭐️4.1、Windows版安装 ⭐️4.2、Linux安装 ⭐️4.3、docker安装 简介&#xff1a; 今日工作中的ETL脚本使用了从晶的…

【通义千问】什么是通义千问,如何免费获得内测和使用方法。

什么是通义千问&#xff0c;如何免费获得内测和使用方法。 什么是通义千问怎么获得内测资格申请方法有两种第一种直接点击申请体验第二种直接点击使用邀请码 通义千问邀请码怎么获得参与社区活动邀请好友关注通义千问微信公众号参加通义千问线上课程向通义千问官方提问 通义千问…

【C++】4.jsoncpp库:jsoncpp库安装与使用入门

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍jsoncpp的使用。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习知识&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&am…

Apache Kafka - ConsumerInterceptor 实战(2)

文章目录 Pre思路示例配置文件自定义 拦截器使用测试 小结 Pre Apache Kafka - ConsumerInterceptor 实战 (1) 用代码的方式实现了ConsumerInterceptor , 接下来我们用 配置的方式来实现一下 。 思路 如何找配置类 KafkaProperties 有些属性是很明显的有的&#xff0c;其他没…

STM32单片机(二)STM32环境搭建

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

LLMs开源模型们的分布式训练和量化

前一篇博文整理了&#xff1a; LLMs开源模型们和数据集简介 这篇博文主要整理一下目前流行的训练方法和量化。 &#xff08;图自Towards a Unified View of Parameter-Efficient Transfer Learning&#xff09; Tuning Strategies 使通用LLMs适应下游任务的最常见方法是微调…

电气器件系列三十七:多路温度测试仪、温度巡检仪

巡检仪适用于多点测量显示及控制&#xff0c;集多台仪表功能于一体&#xff0c;一般可巡检1&#xff5e;64路测量信号,可巡回检测和显示多路信号&#xff0c;与各类传感器、变送器配合使用&#xff0c;现已开发出八路巡检仪\十六路巡检仪\24路巡检仪-64路可对多路温度、压力、液…

项目总结 车牌识别

代码贴&#xff1a;OpenCV实战5 车牌号识别_opencv车牌字符识别_爱钓鱼的歪猴的博客-CSDN博客 目录 1、效果 2、代码思路 0、准备车配字符模板图片以及字符文件 1、对整图进行预处理 得到突出车牌的cany边缘图 2、车牌字体联通在一起&#xff0c;形成一个区域 3、筛选出车…

【华为OD机试】太阳能板最大面积【2023 B卷|100分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 给航天器一侧加装长方形或正方形的太阳能板(图中的红色斜线区域),需要先安装两个支柱(图中的黑色竖条), 再在支柱的中间部分固定太阳能板。 但航天器不同位置的支柱长度不同,太阳…

URLConnection(一)

文章目录 1. 简介2. 打开URLConnection3. 读取服务器的数据4. 读取首部5. 获取任意首部字段 1. 简介 URLConnection是一个抽象类&#xff0c;表示指向URL指定资源的活动连接。URLConnection有两个不同但相关的用途。首先&#xff0c;与URL类相比&#xff0c;它对服务器&#x…

chatgpt赋能python:简介:什么是PythonShapiro?

简介&#xff1a;什么是Python Shapiro&#xff1f; Python Shapiro是一种用来进行正态性检验的工具&#xff0c;也就是说&#xff0c;它可以帮助我们检验一个给定的数据集是否符合正态分布的要求。它是从R语言中的Shapiro-Wilk测试方法改编而来的。 如何使用Python Shapiro&…

继瑞吉外卖后的又一个项目——SpringBoot+Vue的前后端博客系统

文章目录 博客系统项目介绍前言项目演示前台演示后台演示 组织结构后端组织结构前端组织结构 技术选型前端技术后端技术架构图系统架构图业务架构图 模块介绍前端模块后端模块 环境搭建开发工具开发环境项目运行 未完待续结语 博客系统项目介绍 前言 本项目已开源在Gitee 后端…