冰冰学习笔记:位图与布隆过滤器

news2024/11/15 23:23:51

欢迎各位大佬光临本文章!!! 

还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool


系列文章推荐

冰冰学习笔记:《哈希表与无序容器》

冰冰学习笔记:《管道与共享内存》


目录

系列文章推荐

前言

1.位图

1.1位图的实现

1.2位图的应用题

1.3小结:

2.布隆过滤器

2.1布隆过滤器的简介

2.2布隆过滤器的实现

2.3小结: 


前言

        在学习完哈希表的底层原理与代码实现后,我们需要对哈希表实际的应用进行介绍。位图与布隆过滤器就是典型的两个应用,本文将详细介绍位图与哈希表的底层实现。

1.位图

        位图是哈希表结构的一种变形应用,哈希表是将存储的数据一一映射一个位置。但是当存储的数据量过大,例如存储数据量为100亿个整数时,内存空间无法开辟这么多的存储位置对数据进行绝对映射。但是计算机中每一个比特位都具备0,1两种状态,我们可以使用一个比特位来标记某个数值是否存在,存在设为1,不存在设为0。此时就算100亿个整数的存储我们也可以存储的下,我们只需要开辟整数范围的比特位即可。

        因此使用一个比特位来标记某个数据是否存在的数据结构就称之为位图。

1.1位图的实现

        位图在C++的标准库中是存在的,在头文件<bitset>中,存有一个数据结构类型bitset,我们可以直接使用并创建出自己需要的位图大小。位图中常见的接口有set(),用来设置某个位存在;reset()将某个位复原;count(),统计有多少个位被设置;size(),计算有多少个位;test(),返回某个位是否被设置。并且位图还重载了[ ],可以直接将某个位进行设置。

        那么我们如何实现自己的位图数据结构呢?我们可以使用vector来封装位图即可,底层为一个存储char类型的vector数据结构,一个char类型的数据空间占据8个比特位。当申请位图时我们使用非类型模板参数进行位图大小的申请,N为用户显示传递的申请位图大小,我们申请N/8+1个char类型的空间,这样可以确保我们申请的大小空间一定能够用户使用,并且浪费的空间最小。

        初始化函数直接调用vector的resize开辟空间,并设置为0。

template<size_t N>
class bit_set
{
public:
    bit_set()
    {
        _bt.resize(N/8+1,0);
    }
private:
    vector<char> _bt;
}

        set(size_t x)函数的功能是在用户传递的位置将位图中的比特位设置为1。首先我们需要得知用户传递的x位于第几个插入类型的数据上,然后再获得在该char类型数据的第几个位上。最后将该位置设置为1。

void set(size_t x)
{
	//获得第几个整形位置
	size_t i = x / 8;
	//获得第几个位
	size_t j = x % 8;
	_bt[i] |= (1 << j);
}

        例如我们申请了20个空间的位图,并且设置第18个位置。那么位图会给我们申请3个char类型数据的空间大小。然后set传递的x为18。通过x/8,我们可以计算到是在第2个char数据上,x%8,可以计算到是在第2个位置。随后通过1左移2位并且通过“或等”的方式设置比特位。

        reset(size_t x)函数就是set函数的翻转,同样需要先找到要更改的位置x,然后进行“与等操作”。

void reset(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bt[i] &= ~(1 << j);
}

        test(size_t x)函数是判断是否设置了x位置的比特位,在找到当前位置的比特位后我们只需要与1左移后进行相与操作即可,0与1与的结果是0,1与1的结果为1。size()函数直接返回N即可。

bool test(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	return _bt[i] & (1 << j);
}
size_t count()const
{
	size_t count = 0;
    size_t n=0;
	for (int i = 0; i < _bt.size(); i++)
	{
		for (int j = 0; j < 8; j++)
		{
			if (_bt[i] & (1 << j))
				count++;
            if (n == N) break;
		}
	}
	return count;
}
size_t size()const
{
	return N;
}

        count函数因为需要计算实际用户申请的位图大小中被设置的1的个数,因此我们使用n进行计数,当n到达N的时候跳出循环结束1的计数。

        flip()函数为翻转位图,我们只需要对位图中所有的位与1进行异或即可翻转。 

bit_set& flip()
{
	for (int i = 0; i < _bt.size(); i++)
	{
		_bt[i] ^= 0xFF;
	}
	return *this;
}

1.2位图的应用题

(1)给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中?

        40亿个数字大约有16亿字节,约等于16G的内存空间,如果我们使用搜索树和哈希表都不能存储,内存空间无法开辟。使用排序(N*logN)和二分查找(logN)就必然需要借助磁盘进行操作,此时就会降低效率并且不好进行二分操作。

        如果使用位图,我们只需要开辟整数范围个位来标记即可,整数的最大范围位2^32次方,一共需要512MB的空间就可以存储下。时间复杂度是O(1)。 

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

        使用两个位图分别存储两个文件,然后两个位图相与或者遍历两个位图,都存在的的数据即为交集。 

(3)给定100亿个整数,设定算法找到只出现一次的整数

        100以各整数里面必然存在大量的数据重复,因为整数一共大约有42亿个,由于需要找到只出现一次的整数,那么这些数据就可以分为出现1次,出现2次及以上,不出现,三种状态。因此我们需要改进位图,使用两个位标识一个数据的状态,此时需要1G的内存空间即可完成。

(4)1个文件有100一个整形数据,1G内存,设计算法找到出现次数不超过2次的所有整数

        也是需要使用两个位的位图进行实现,00标识未出现,01标识出现1次,10标识出现2次,11标识出现2次以上。遍历位图找到00,01,10标识的下标即为出现不超过2次的数据。

使用位图封装2个位的改进位图代码:

	#define MULTIPLE -1
	template<size_t N>
	class twobit_set
	{
	public:
		void set(size_t x)
		{
			bool ret1 = _bt1.test(x);
			bool ret2 = _bt2.test(x);
			if (ret1 == false && ret2 == false)
			{
				//第一遇到 00->01
				_bt2.set(x);
			}
			else if (ret1 == false && ret2 == true)
			{
				//第二次遇到 01->10
				_bt1.set(x);
				_bt2.reset(x);
			}
			else if (ret1 == true && ret2 == false)
			{
				//两次以上 01->11
				_bt1.set(x);
				_bt2.set(x);
			}
		}
		void reset(size_t x)
		{
			_bt1.reset(x);
			_bt2.reset(x);
		}
		bool test(size_t x)
		{
			return (_bt1.set(x) || _bt2.set(x));
		}
		size_t frequency(size_t x)
		{
			bool ret1 = _bt1.test(x);
			bool ret2 = _bt2.test(x);
			if (ret1 == false && ret2 == false)
			{
				//出现0次
				return 0;
			}
			else if (ret1 == false && ret2 == true)
			{
				//出现一次
				return 1;
			}
			else if (ret1 == true && ret2 == false)
			{
				//出现2次
				return 2;
			}
			else
			{
				//两次以上
				return MULTIPLE;
			}
		}
		
		void print_once()
		{
			for (int i = 0; i < N; i++)
			{
				if (frequency(i) == 1)
				{
					cout << i << " ";
				}
			}
			cout << endl;
		}
		void print_twice()
		{
			for (int i = 0; i < N; i++)
			{
				if (frequency(i) == 2)
				{
					cout << i << " ";
				}
			}
			cout << endl;
		}
	private:
		bitset<N> _bt1;
		bitset<N> _bt2;
	};

1.3小结:

        位图在处理大量数据的时候具备优势,速度块并且节省空间,使用的是直接定址法,不存在哈希冲突。但是位图相对局限,只能处理整数。

2.布隆过滤器

2.1布隆过滤器的简介

        在生活中我们难免需要剔除一些我们不需要的信息数据,设置一些黑名单。那么如何过滤这些我们不想看到的内容呢?如果使用哈希表存储这些内容,每次都进行查找比对,必然会浪费大量的空间,而位图又不能处理字符数据。基于这种情况,科学家提出了布隆过滤器。

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

        我们将某个数据通过一些特殊的映射算法将其传化为不同的几个整数,并将这几个整数存放在位图中。当查询这个数据在不在时,我们只需要使用映射算法先计算出该数据在位图中的存储位置,然后检测位图中对应的位置是否被设置即可,只要有一个位置没有被设置那么就意味着数据不存在。但是即便位图对应的位置都被设置了也不一定能够确定该数据存在,原因在于可能出现冲突进行了误判。

例如,我们使用3个位来标识一些字符串是否存在,如下图所示:

        在图中,我们将百度,华为,小米,腾讯都设置为存在,三个对应的位都被设置,但是当我们查询阿里时,会发现阿里也被标记存在了,原因在于阿里的标记比特位为4,16,24与之前设置的字符中标记位发生了冲突,位置早就被设置,所以出现了误判。当我们查询字节时,即便13,30早就被设置,但是字节对应的另一个比特位22没有被设置,那么字节就一定不存在。

        因此布隆过滤器在标记不存在状态时是准确的,存在状态并不准确,会有误判。理论而言,为了降低误判率我们可以使用多个位进行映射一个数据,但是一个数据映射的位越多,空间消耗就会越大。

2.2布隆过滤器的实现

        布隆过滤器的实现并不复杂,难点在于进行字符串转换整数的函数,我们使用哈希关键字算法中的三个字符串转换函数进行实现。布隆过滤器中不支持删除,我们只需要实现set函数和test函数即可。

实现代码:

namespace lb
{
	struct HashBKDR
	{
		// BKDR
		size_t operator()(const string & key)
		{
			size_t val = 0;
			for (auto ch : key)
			{
				val *= 131;
				val += ch;
			}

			return val;
		}
	};

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

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

			return hash;
		}
	};
	//数据范围N,存储类型K,3种转化算法
	template<size_t N,
	class K=string,class Hash1=HashBKDR,class Hash2=HashAP,class Hash3= HashDJB>
	class BoolmFilter
	{
	public:
		void set(const K& key)
		{
			size_t hash1 = Hash1()(key) % (N * _ratio);
			_bf.set(hash1);
			size_t hash2 = Hash2()(key) % (N * _ratio);
			_bf.set(hash2); 
			size_t hash3 = Hash3()(key) % (N * _ratio);
			_bf.set(hash3);
		}
		bool test(const K& key)
		{
			size_t hash1 = Hash1()(key) % (N * _ratio);
			if (!_bf.test(hash1))
				return false;
			size_t hash2 = Hash2()(key) % (N * _ratio);
			if (!_bf.test(hash2))
				return false;
			size_t hash3 = Hash3()(key) % (N * _ratio);
			if (!_bf.test(hash3))
				return false;//准确判断
			return true;//存在误判
		}
		//布隆过滤器不支持删除
	private:
		const static size_t _ratio = 5;//每个数据需要几个位保存
		bitset<N * _ratio> _bf;
	};
}

2.3小结: 

        布隆过滤器并不支持删除,原因在于删除操作有可能影响其他元素,例如我们开头举出的例子,如果我们将阿里删除,那么华为,小米,腾讯的位图都受到了影响,查找时将标记为不存在。布隆过滤器具备以下优点和缺点:

优点:

1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关

2. 哈希函数相互之间没有关系,方便硬件并行运算

3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势

5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

缺点:

1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再 建立一个白名单,存储可能会误判的数据)

2. 不能获取元素本身

3. 一般情况下不能从布隆过滤器中删除元素 4. 如果采用计数方式删除,可能会存在计数回绕问题

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

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

相关文章

MySQL复制技术方案——半同步复制配置

Google为MySQL和InnoDB设计了一个大规模补丁集以量身打造服务器和存储引擎。其中一个修补程序可用于MySQL5.0版本&#xff0c;是半同步的复制补丁。MySQL已经打上了该补丁并在MySQL5.5中发布了。 半同步复制的理念是在允许更改操作继续执行前&#xff0c;确保更改操作至少被写…

34、基于STM32的电子时钟设计(DS1302)时钟、秒表、倒计时(Proteus仿真+程序)

编号&#xff1a;34 基于STM32的电子时钟设计&#xff08;DS1302&#xff09;时钟、秒表、倒计时 功能描述&#xff1a; 本系统由STM32F103系统LCD1602液晶显示按键模块DS1302时钟模块声光报警模块组成。 1、使用LCD1602显示当前日期、时间、星期 2、具有闹钟、倒计时、计时功…

【Java寒假打卡】Java基础-抽象类

【Java寒假打卡】Java基础-抽象类一、概述二、抽象类注意事项三、模板设计模式四、final关键字五、代码块一、概述 抽象方法&#xff1a;将共性的方法抽取到父类之后&#xff0c;发现该方法的实现逻辑无法在父类中给出具体明确&#xff0c;该方法就可定义为抽象方法抽象类&…

【C++初阶8-vector】熟悉的ta

前言 本期看看这位熟悉又陌生的朋友——vector。 博主水平有限&#xff0c;不足之处望请斧正&#xff01; 是什么 vecotr是序列容器&#xff0c;可变大小的数组。 *vector有矢量、向量的意思&#xff0c;用其命名可能想强调“序列”这个概念。 class template std::vecto…

独占指针 std::unique_ptr

学习智能指针之前需要知道的&#xff1a; 智能指针是原始指针的封装&#xff0c;在头文件<memory>中&#xff0c;优点就是自动分配内存&#xff0c;不用担心潜在的内存泄漏。不是所有的指针都可以封装成智能指针&#xff0c;很多时候原始指针更方便。各指针中&#xff0…

Webpack中常见的Loader?解决了什么问题?

一、是什么 loader 用于对模块的源代码进行转换&#xff0c;在 import 或"加载"模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。如下图所示&#xff1a; 在web…

【网络安全】——web渗透的前缀知识

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

ArcGIS基础实验操作100例--实验18合并表格

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验18 合并表格 目录 一、实验背景 二、实验数据 三、实验步骤 方法一&#xff1a;导出…

whisper

Robust Speech Recognition via Large-Scale Weak Supervision 介绍 大规模弱监督的训练。先前的方法都是通过大量的无监督学习训练&#xff08;无监督的数据容易收集&#xff0c;所以通过大量无监督的学习可以训练出一个质量较好的encoder&#xff09;。但是用的时候还需要找…

Redis配置文件

Redis配置文件 自定义目录 /myreids/redis.conf Units 单位 配置大小单位&#xff0c;开头定义了一些基本的度量单位&#xff0c;只支持bytes&#xff0c;不支持bit。大小写不敏感 INCLUDES 包含 多实例的情况可以把公用的配置文件提取出来 网络配置相关 bind 默认情况…

【WSL】[01] windows subsytem linux 配置和使用 - ubuntu GUI安装

第【1】章前言&#xff1a; AI的训练和设计似乎ubuntu是必要的&#xff0c;而且&#xff0c;GPU的配置似乎也是要在Ubuntu下&#xff0c;某些模式版本才能兼容。单独搞一个编译服务器是个思路&#xff0c;但是&#xff0c;如果资金不够&#xff0c;也许要考虑在Windwos和Linux…

程序员出身备考PMP,如何避开备考误区顺利拿到3A成绩?

还在犹豫2023年如何才能提升自己的职场竞争力吗&#xff1f;PMP项目管理证书值得大家了解。掌握这些备考技巧&#xff0c;让你的PMP学习少走弯路。有计划明年3月参考PMP的小伙伴注意啦&#xff01; 今天小赛邀请了一位程序员出身的小伙伴&#xff0c;一起来看看他是如何在忙碌…

linux下如何使用configure/make/make install命令编译安装卸载程序

源码的安装一般由3个步骤组成&#xff1a;配置&#xff08;configure&#xff09;、编译&#xff08;make&#xff09;、安装&#xff08;make install&#xff09;。安装成功的源码就是所谓的可执行文件&#xff0c;在你不需要的时候&#xff0c;也是可以删除/卸载&#xff08…

leetcode1456. 定长子串中元音的最大数目

给你字符串 s 和整数 k 。 请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 英文中的 元音字母 为&#xff08;a, e, i, o, u&#xff09;。 示例 1&#xff1a; 输入&#xff1a;s “abciiidef”, k 3 输出&#xff1a;3 解释&#xff1a;子字符串…

Spring的7种事务传播方式

Spring事务传播行为体现在某个service方法调用另一个service方法&#xff0c;事务该如何进行下去。 Spring支持7中事务传播方式&#xff0c;在Propagation类中可以看到&#xff0c;如下&#xff1a; REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED…

【操作系统】进程的属性及状态(三态五态七态)

文章目录进程的概念进程的属性1、结构性2、共享性3、动态性4、独立性5、制约性6、并发性进程状态1、三态模型2、五态模型3、七态模型进程的概念 程进程是一个可并发执行的具有独立功能的程序关于某个数据集合的一次执行过程&#xff0c;也是操作系统进行资源分配和保护的基本…

Java中内部类的讲解(Java系列8)

目录 前言&#xff1a; 1.内部类 1.1内部类的概念 1.2内部类的分类 1.2.1实例内部类 1.2.2静态内部类 1.2.3局部内部类 1.2.4匿名内部类 结束语&#xff1a; 前言&#xff1a; 这次小编主要与大家分享一下什么是内部类&#xff0c;那么接下来就和小编一起来见识一下内…

aloam学习笔记(一)

开始学习aloam框架&#xff0c;记录一下最开始运行aloam中出现的各种问题以及解决方式。 1.aloam地址 GitHub - HKUST-Aerial-Robotics/A-LOAM: Advanced implementation of LOAM 2.安装aloam的一些依赖 主要是两个ceres和pcl库 2.1安装ceres ceres官方地址&#xff1a;I…

宝马335i手动挡和M3手动挡的对比

感受篇 *动力 两车的动力都堪称强悍&#xff0c;但发力感受差异非常大。335具备典型的涡轮车特征&#xff0c;动力来的比较突兀&#xff0c; 低速跟车时油门很难控制&#xff0c;给小了车走得慢&#xff0c;给大了就往前窜。转速拉到3000转以后335的动力刺激度非常高&#xff…

房产管理系统安全可靠性分析

数图互通房产管理 高校房产管理系统是基于公司自主研发FMCenter平台开发的应用系统。 一、系统安全性分析&#xff1a; 1.支持SSL传输协议&#xff0c;可以实现链路层的加密传输。 2.提供基于角色的授权体系&#xff0c;角色可自…