【C++】位图、布隆过滤器概念与模拟实现

news2024/11/28 2:37:50

目录

一、位图

1.1 位图的概念

1.2 位图的使用

1.3 位图的实现

1.4 位图的应用

二、布隆过滤器

2.1 布隆过滤器

2.2 布隆过滤器的实现

2.3 布隆过滤器练习题


一、位图

1.1 位图的概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在。

1.2 位图的使用

首先我们来看一道题目:

给定40亿个不重复的无符号整数,没有进行排序。现在给一个无符号整形,如何快速判断一个数是否存在这40亿个数中。

现在有三种方法:

1.遍历,时间复杂度O(N)

2.排序后使用二分查找,时间复杂度为:排序(O(N logN)) + 二分查找(O(logN))

3.位图

如果我们使用位图解决该的问题,我们只需要开辟一个40亿个 bit 的空间(如果直接存放40亿的整数约占16G,开辟40亿bit约占512MB).

使用直接定址法进行映射,如果该位置是0,则表示该数据不存在,如果是1表示该数据存在。

如下图:

1.3 位图的实现

接下来是位图的接口展示:

template<size_t N>
class bit_set
{
public:
	//默认构造
	bit_set()
	{}

	//将映射的地方改为1
	void set(size_t x)
	{}

	//删除数据
	void reset(size_t x)  
	{}

	//判断x在不在
	bool test(size_t x)
	{}
private:
	vector<char> _bits;
};

我们可以设置一个非模板参数来控制开辟空间的大小,在构造函数中进行空间的开辟。

bit_set()
{
	_bits.resize(N / 8 + 1, 0);
}

接下来就是 set 的编写了,目的就是将映射的地址改为1即可,我们使用/8求出该值在第几个char上,再进行模8求出在第几位上,再进行进行位移+或的方式进行即可:

//将映射的地方改为1
void set(size_t x)
{
	//1.除8再模8
	size_t i = x / 8;     //求在第几个char处
	size_t j = x % 8;     //求在第几位上
	_bits[i] |= (1 << j); 
}

reset表示删除该数,我们直接将该bit位上的数据置为0即可,我们找到该将1左移到该位置上,然后使用取反操作,这样除了第j位的都是1,再进行与操作,即可完成数据的删除。

void reset(size_t x)  //删除这个数据
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] &= ~(1 << j);    //左移取反再 与
}

test接口就是将传入的数据的映射位直接返回即可。

bool test(size_t x)//判断x在不在
{
	size_t i = x / 8;
	size_t j = x % 8;
	return _bits[i] & (1 << j);
}

写完之后我们来测试一下:

注意,这里其实不用关注当前是小端存储还是大端存储,因为我们的存储规则和查询规则是一致的,其中我们的位操作(例如左移,本质上是从低地址往高地址进行位移,而不是方向的位移)。

1.4 位图的应用

  • 给定100亿个int,1G内存,设计算法找到只出现一次的整数。

第一题:

首先,1G内存大约有80亿的bit位,而100亿个int,int 最多能表示大约42亿9千万个数,也就是说100亿的数据一半以上都是重复的;我们只用43亿个bit位就可以解决该问题,所以这里使用1G空间完全可以解决该问题。

这是一个KV统计搜索模型,我们可以使用两个位图来解决,用两个位图中对应位置的值来表示这个整数的出现情况:

0次  --->   00

1次  --->   01

2次及以上---> 10

 其实在STL库中就有位图容器,我们可以直接进行使用:

 接下来就是set的实现,首先要检测两张位图中该数据的存在情况,然后根据其状态做出处理:

void set(size_t x)
{
	bool inset1 = _bs1.test(x);      //检测当前数据存在情况
	bool inset2 = _bs2.test(x);      //检测当前数据存在情况

	// 00 -> 01  表示出现一次
	if (inset1 == false && inset2 == false)
	{
		_bs2.set(x);
	}
	// 01 -> 10  表示出现一次
	else if (inset1 == false && inset2 == true)
	{
		_bs1.set(x);
		_bs2.reset(x);
	}
}

test的实现就是位图1中该位置是0并且位图2该位置是1则为真,反之则为假:

bool test(size_t x)
{
	return (_bs1.test(x) == false && _bs2.test(x) == true);
}

接下来我们可以写一个打印当前位图存放了所有数据的Print函数:

void Print_once_num()
{
	cout << "Print_once_num:" << endl;
	for (size_t i = 0; i < N; i++)
	{
		if (_bs1.test(i) == false && _bs2.test(i) == true)
		{
			cout << i << " ";
		}
	}
	cout << endl;
}

然后我们写一段代码简单测试一下我们的双位图结构:

位图快并且节省空间,但是其局限在于只能处理整形,接下来我们就来学习布隆过滤器来处理字符串。

二、布隆过滤器

2.1 布隆过滤器

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

如上图,x、y、z都映射了3处,但是发现 x 和 z 以及 y 和 z 有相同的映射处,这就说明布隆过滤器是存在不准确的情况。

再观察W,w不是过滤器中的值,进行检测映射后发现一个位置为0,则能表示w不在过滤器中。这便能得出结论。

误判情况:

存在:不准确,有可能是其它数据也映射到了此处。

不存在:准确,表示该值并没有把其应该映射的位置进行修改。

布隆过滤器的存在的误判是被允许的,因为在很多场景需要快速地进行判断。

  • 比如游戏中的起网名,服务器不可能将你的游戏 ID 拿到数据库中进行查询,而是直接将你的游戏 ID 在过滤器中进行查询,如果过滤器查询结果是 ID 已存在,系统则提示你 ID 被占用。即使这个ID在数据库中并不存在,但是这样的操作节省了服务器的运行压力。
  • 再比如网络失信名单,将身份证号在失信名单过滤器中进行查询,如果查询结果显示为失信人员,则再由服务器将身份证在数据库中进行二次查询;而如果显示非失信人员时,直接返回结果即可。

所以,布隆过滤器是非常适合字符串的快速查询,即使存在缺陷,但是我们可以采取多次映射的方式,即使用不同的字符串哈希算法,来降低误判的几率。

理论而言:一个值映射的位越多或表的长度越长,误判概率越低。但是也不能映射太多,不然会导致布隆过滤器优势丧失。

这有一篇相关的证明博客:详解布隆过滤器的原理,使用场景和注意事项

根据上面博客的中的内容,使用越多的字符串哈希函数其冲突率会逐渐降低。

接下来我们分析我们应该如何设计m和k,即过滤器长度和哈希函数的个数

 所以,接下来的布隆过滤器的实现,比如我们要标记N个数,则应开辟4.2*N以上的空间(方便计算取5)

2.2 布隆过滤器的实现

布隆过滤器的底层使用的位图来进行记录数据,这次模拟实现使用3套哈希函数,所以要设置5个模板参数(1.数据个数;2.数据类型;3.哈希函数1;4哈希函数2;5.哈希函数3)

1.哈希函数

注意:这次是使用字符串类型进行测试,所以哈希函数都是字符串的哈希函数;如果想让过滤器支持自定义类型直接编写对应的哈希函数即可。

各种字符串哈希函数:各种字符串Hash函数

这里直接使用几种常见的字符串哈希函数进行用于传参即可,如下:

struct HashString1
{
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val = val * 131 + ch;
		}
		return val;
	}
};

struct HashString2
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};
struct HashString3
{
	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;
	}
};

2.标记数据

过滤器的标记则是使用传入的哈希函数算出映射位置,然后调用位图得 set 进行标记即可。

void Set(const K& key)
{
	//将哈希函数映射处进行标记
	size_t hash1 = Hash1()(key) % (_ratio * N);
	size_t hash2 = Hash2()(key) % (_ratio * N);
	size_t hash3 = Hash3()(key) % (_ratio * N);

	_bits.set(hash3);
	_bits.set(hash1);
	_bits.set(hash2);
}

3.查询数据

查询数据其实就是找对应的映射位置,如果3个映射位置有一个为0,则表示数据不存在,并且该结果准确,如果三个都为1,则表示该数据可能存在,这是布隆过滤器不可避免的问题。

实现方式是根据哈希函数求出对应的3个映射位置,然后使用位图的 test,如果有一处为0则返回false,反之返回true;

bool Test()
{
	//检测对应的3处标记为位
	size_t hash1 = Hash1()(key) % (_ratio * N);
	size_t hash2 = Hash2()(key) % (_ratio * N);
	size_t hash3 = Hash3()(key) % (_ratio * N);
	//3处都不为零返回真,1处为假则返回假
	if (_bits.test(hash1) && _bits.test(hash2) && _bits.test(hash3))
		return true;
	return false;
}

4. 效果测试

测试思路:插入一组字符串arr1,然后让arr2中的字符串进行查询,观察查询结果。

void TestBloomFilter1()
{
	string arr[] = { "苹果","西瓜","菠萝","草莓","梨子","葡萄"};
	BloomFilter<100, string, HashString1, HashString2, HashString3> bf;
	for (auto str : arr)
	{
		bf.Set(str);
	}
	cout << "Test:" << endl;
	string arr2[] = { "梨子","苹果","草莓","李子","西瓜2"," ","字符","排序"};
	for (auto str : arr2)
	{
		cout << str << ":";
		if (bf.Test(str)) cout << "存在" << endl;
		else cout << "不存在" << endl;
	}
}

 5. 误判率的检测

接下来是一段测试误判率的代码

void TestBloomFilter2()
{
	srand(time(0));
	const size_t N = 100000;
	BloomFilter<100000, string, HashString1, HashString2, HashString3> bf;
	cout << sizeof(bf) << endl;

	std::vector<std::string> v1;
	std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";

	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(1234 + i));
	}

	for (auto& str : v1)
	{
		bf.Set(str);
	}

	// 相似
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string url = "http://www.cnblogs.com/-clq/archive/2021/05/31/2528153.html";
		url += std::to_string(99999999 + i);
		v2.push_back(url);
	}

	size_t n2 = 0;
	for (auto& str : v2)
	{
		if (bf.Test(str))
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	std::vector<std::string> v3;
	for (size_t i = 0; i < N; ++i)
	{
		string url = "zhihu.com";
		url += std::to_string(rand() + i);
		v3.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.Test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

2.3 布隆过滤器练习题

第一题

  • 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。

首先,query表示的是请求,比如网络请求,SQL语句,本质就是字符串。

其次,近似算法的意思是允许该算法存在一些误判,而精确算法的要求是绝对准确。

近似算法:

使用布隆过滤器:

答:将 A 文件中的数据放到布隆过滤器中,然后遍历 B 文件中的数据与布隆过滤器中的数据进行比较,如果是存在则是交集,不存在则不是交集。

但是这种方法虽然实现简单,但是存在误判重复项过多两个问题。

精确算法:

接下来介绍一种思想:哈希切分

  1. 假设每个query占30byte,100亿query则是3000亿byte,则约为300G(1G约10亿byte)。
  2. 然后我们将每一个 query 使用哈希函数转为整形,将该整形进行取模放入对应的 i 文件中,则相同的 query 就被放到了相同编号的小文件
  3. 让 Ai 与 Bi 的文件放入内存一一进行对比,如果对比结果相同,则是交集。


第二题

  • 如果扩展BloomFilter使得它支持删除元素的操作。

布隆过滤器不支持删除操作。因为删除一个标记位可能会影响其它的数据在改为的映射关系。如果想要实现删除操作,可以使用引用计数的思路来实现BloomFilter的删除操作,但是如果使用引用计数的方式支持删除,空间消耗会更多,会导致BloomFilter的优势消失。


第三题

  • 给一个超过100G大小的 log file,log中存着 IP 地址,设计算法找到出现次数最多的 IP 地址?那如果找到 top K 的 IP 呢?

解决方式:

  1. 读取每个ip,i=Hash(ip)%500,即 ip 进行第 i 个文件。
  2. 依次使用map<string,int>,对每个小文件统计次数,即映射的 ip 最多的文件。
  3. 取出出现次数最多的文件,然后建立K个值为<ip,count>的小堆,即可求出出现次数最多的K个ip。

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

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

相关文章

监控Python 内存使用情况和代码执行时间

我的代码的哪些部分运行时间最长、内存最多&#xff1f;我怎样才能找到需要改进的地方&#xff1f;” 在开发过程中&#xff0c;我很确定我们大多数人都会想知道这一点&#xff0c;而且通常情况下存在开发空间。在本文中总结了一些方法来监控 Python 代码的时间和内存使用情况…

【24】C语言 | 调试技巧

目录 1、调试概念&#xff1a; 2、Debug和Release的介绍 3、windows中的快捷键 4、案例一&#xff1a;求1&#xff01; 2&#xff01;3&#xff01;...n&#xff01; 5、案例二&#xff1a;下面的代码输出什么&#xff1f; 6、案例三&#xff1a;实现一个strcopy的函数 …

零入门容器云网络实战-3->Underlay网络与Overlay网络总结

本篇文章主要用于收集、整理、总结关于Underlay网络以及overlay网络相关知识点。 1、underlay网络介绍&#xff1f; 1.1、什么是underlay网络&#xff1f; Underlay网络就是&#xff1a; 传统IT基础设施网络&#xff0c;由交换机和路由器等设备组成&#xff0c;借助以太网协议…

3分钟搭建起聊天机器人需要的NoneBot2环境

创建nonebot2运行环境 官网上说这里的Python版本要高于3.8.0&#xff0c;还会有其他的依赖。 所以这里推荐大家使用虚拟环境&#xff0c;Poetry、venv、Conda&#xff0c;我这里用的是conda环境&#xff08;不同的项目依赖可能有所不同&#xff0c;所以才创建虚拟环境&#xf…

[羊城杯 2020]EasySer

目录 信息搜集 代码审计 参数扫描 信息搜集 先扫下目录 .htaccess&#xff1b;robots.txt&#xff1b;flag.php&#xff1b;index.php 在robots.txt下看到了/star1.php 进入star1.php发现出现ser.php <!-- 小胖说用个不安全的协议从我家才能进ser.php呢&#xff01; !--…

蓝桥杯刷题015——最少刷题数(二分法+前缀和)

问题描述 小蓝老师教的编程课有 N 名学生, 编号依次是 1…N 。第 i 号学生这学期刷题的数量是 Ai​ 。 对于每一名学生, 请你计算他至少还要再刷多少道题, 才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。 输入格式 第一行包含一个正整数 N 。 第二行包含 N 个整数:…

学成在线项目开发技巧整理---第一部分

学成在线项目开发技巧整理---第一部分1.数据字典2.http-client远程测试插件,可以保存测试数据3.三种跨域解决4.具有多层级数据查询思路5.Mybaits分页插件原理6.根据文件后缀解析出mime-type7.大文件上传8.Spring事务什么时候会失效9.分布式文件系统MinIo10.构建独立文件系统11.…

3.3Sram和Dram

文章目录一、引子二、存储元件1.DRAM芯片&#xff08;1&#xff09;栅极电容1&#xff09;存储2&#xff09;读出&#xff08;2&#xff09;物理特性&#xff08;3&#xff09;DRAM刷新&#xff08;4&#xff09;DRAM地址线复用2.SRAM芯片&#xff08;1&#xff09;双稳态触发器…

爬虫之JS的解析

JS的解析 学习目标&#xff1a; 了解 定位js的方法了解 添加断点观察js的执行过程的方法应用 js2py获取js的方法 1 确定js的位置 对于前面人人网的案例&#xff0c;我们知道了url地址中有部分参数&#xff0c;但是参数是如何生成的呢&#xff1f; 毫无疑问&#xff0c;参数肯…

gin全解

文章目录介绍安装快速开始&#xff08;三种启动方式&#xff09;参数获取querystring参数其他不常用方法表单参数&#xff08;form参数&#xff09;其他不常用方法获取path参数参数绑定文件上传单个文件多个文件请求&#xff08;ctx.Request)响应gin.H{}字符串响应JSON/YAML/TO…

一起自学SLAM算法:8.2 Cartographer算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; Gmapping代码实现相对简洁&#xff0c;非常适合初学者入门学习。但是Gmapping属于基于滤波方法的SLAM系统&#xff0c;明显的缺点是无法构建大规模的地图&#xff0c;这一点已经在第7章中讨论过了。而基于优化的…

Python爬虫之findall和lxml

Python爬虫之findall和lxml 提示&#xff1a;前言 Python爬虫之findall和lxml 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录Python爬虫之findall和lxml前言一、导入包二、设置URL和获取视频链接三、解析视频名字…

31. 实战:PyQuery获取小电视Top100详细信息(文末源码)

目录 前言 &#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09; 目的 &#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09;&#xff08;链接放…

趣味三角——第4章——三角学迈向解析化

第4章 三角学迈向解析化(或分析化) 目录 4.1 三角学迈向解析化的过程简述 4.2 Franois Vieter对三角学解析化的贡献 “Thus the analysis of angular sections involves geometric and arithmetic secrets which hitherto have been penetrated by no one(因此&#xf…

Idea中指定xml文件失效

目录一、&#x1f407; 项目场景&#xff1a;二、&#x1f407; 问题描述三、&#x1f407; 原因分析&#xff1a;四、&#x1f407; 解决方案&#xff1a;一、&#x1f407; 项目场景&#xff1a; 最近狮子在搞一个项目&#xff0c;需要用到数据库多表查询&#xff0c;所以在…

数据挖掘,计算机网络、操作系统刷题笔记35

数据挖掘&#xff0c;计算机网络、操作系统刷题笔记35 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;orac…

【论文翻译】Jointformer :一种基于误差预测和改进的三维人体姿态估计的单帧提升变压器

摘要 单目三维人体姿态估计技术有望极大地提高人体运动数据的可用性。表现最好的单幅图像2D3D提升模型使用图卷积网络(GCNs)&#xff0c;通常需要一些手动输入来定义不同身体关节之间的关系。我们提出了一种新的基于变压器的方法&#xff0c;该方法使用更广泛的自我注意机制来…

nodejs+vue高校网上报名系统

本课题利用nodejsVue设计实现网上报名系统。系统的主要功能是&#xff1a;用户在线注册信息之后&#xff0c;利用注册时填写的用户账号与密码&#xff0c;登入系统后&#xff0c;对注册的个人信息进行修改&#xff0c;在线报名&#xff0c;能正确的提交有送报考的基本信息&…

【图卷积网络】01-卷积神经网络:从欧氏空间到非欧氏空间

人工神经网络发展浪潮 第三次浪潮——卷积神经网络 加拿大多伦多大学教授&#xff0c;机器学习领域泰斗Geoffery Hinton及其学生在《科学》上发表了一篇论文 &#xff08;Hinton, G. E . Reducing the Dimensionality of Data with Neural Networks[J]. Science, 2006, 313(578…

【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

教程来自freecodeCamp&#xff1a;【英字】使用 React 和 TypeScript 构建应用程序 跟做&#xff0c;仅记录用 其他资料&#xff1a;https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/ 第二天 以下是视频(0:18-0:40) 的内容 目录第二天1 App 函数…