【C++和数据结构】位图和布隆过滤器

news2024/11/18 14:53:33

目录

一、位图

1、位图的概念 

2、位图的实现 

 ①、基本结构

②、set 

③、reset:

④、test 

⑤、问题:

⑥、位图优缺点及应用: 

⑦、完整代码及测试

二、布隆过滤器

1、布隆过滤器的提出

2、布隆过滤器的实现

①、基本结构

②、三个Hash仿函数实现

③、 set

④、 test

⑤、 删除

 ⑥、完整代码及测试

⑦、 优缺点


一、位图

1、位图的概念 

1. 面试题
40 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
40 亿个数中。【腾讯】
查找一个数在不在,其实就是key模型,那常见的几种解法如下:
  • 1. 遍历,时间复杂度O(N)
  • 2. 排序(O(NlogN)),利用二分查找: logN
  • 3. 位图解决

前两个方案的问题:数据量太大,放不到内存中。

我们可以先考虑40亿个不重复的无符号整数占多少空间?
10亿个字节是1个G,而40亿个整数,一个整数4个字节,即需要160亿(9个0)个字节,即需要16G,这些数据太占空间了,内存根本存不下。

位图是如何解决的呢?

所谓位图,就是用每一bit位来存放某种状态适用于海量数据,数据无重复的场景。通常是用 来判断某个数据存不存在的。数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一 个二进制比特位来代表数据是否存在的信息,如果二进制比特位为 1 ,代表存在,为0 代表不存在。(位图是直接定址法的一种)
而40亿个数据,我们起码要开42亿个空间,为什么?假如有一个数是4200000000,你要映射到哪个位置?我们开空间是要开数据的范围,才能满足全部映射进去。
那我们直接开2^32个空间(这里说的每个位置就是比特位,2^32≈42亿9千万,因为位图就是每个位置存的bit位),来对所有数直接定址法建立映射关系,即我们开辟42亿9千万个bit位,先把42亿9千万看成字节,则其≈4G,而4G≈4000M(或者MB),而这里是比特位,故要除以8,4000/8=500M,故位图存储占用500M即可,这既节省了空间,效率又很高(效率高,因为直接定址法没哈希冲突)

2、位图的实现 

 ①、基本结构

位图的初始化应该是开多少个比特位,因为位图就是利用存比特位来节省的空间,你传的N也是比特位,其次是vector的类型是int,因为类型不支持是比特位的

class bitset
{
public:
	bitset(size_t N)
	{//N代表要开多少比特位(简称:位)
		_bits.resize(N / 32 + 1, 0);
		_num = N;
	}

private:
	//int* _bits;
	std::vector<int> _bits;
	size_t _num;  //表示存了多少个比特位,映射存储的多少个数据
};
_bits.resize(N / 32 + 1, 0);

为什么要N / 32 + 1 呢?

因为vectorresize开空间是以整形为单位的,而位图的每个位置存的都是一个比特位,而一个整形32个比特位,N代表的是比特位的位数,则N/32得到的是整形的个数,但是还需+1,为什么?比如N=100,100/32=3,那意思就是开3个整形,即32*3=96个位,但是100还是没位置放,你就开了96个位(同理97,98...都没位置),为了避免这个问题我们会+1,即多开一个整形,那最多只会浪费31个位


②、set 

功能:第x个位设置为1,表示这个位存在

void set(size_t x)
{//把第x位设置为1,表示此位置存在

	size_t index = x / 32; //算出映射的位置在第几个整数
	size_t pos = x % 32;   //算出x在整数的第几个位

	_bits[index] |= (1 << pos); //对于这个整数第pos的位置或1
	//1<<pos表示先把1移动到和pos相同位置的比特位上,因为pos=几,1就会左移几位
	//|=表示是或,即除了pos位置的位要变成1,其余位置都不受影响
}

 其实就是考察怎么把一个整数的某个位变成1,还不影响其他的31位

小端机的左移是向左 , 大端机的左移是向右

这是c语言设计的bug,历史遗留问题,易让人误导,计算机技术发展百花齐放,再融合统一


③、reset:

功能:第x个位置成0,表示这个位不存在

void reset(size_t x)
{//把第x位设置为0,表示此位置不存在

	size_t index = x / 32; //算出映射的位置在第几个整数
	size_t pos = x % 32;   //算出x在整数的第几个位

	_bits[index] &= ~(1 << pos); //对于这个整数第pos的位置与0
}


④、test 

功能:判断第x位在不在(即判断x所映射的位是否为1)

//判断x在不在(也就是说x映射的位是否为1)
bool test(size_t x)
{
	size_t index = x / 32; //算出映射的位置在第几个整数
	size_t pos = x % 32;   //算出x在整数的第几个位

	return _bits[index] & (1 << pos); //结果非0为真,0则为假
}


⑤、问题:

理论上这40亿个数不可能存在内存当中,应该存在文件中,那我们就要去读文件,40亿个数因为要按范围开,故要开42亿9千万的空间,怎么开这么大的空间?常见方法如下:

①、bitset bs(-1);  //因为位图的构造函数参数是size_t,那-1若看为无符号数就是整形的最大值

②、bitset bs(0xffffffff);


⑥、位图优缺点及应用: 

优点:节省空间、效率高

缺点:只能处理整形

应用:

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


⑦、完整代码及测试

#pragma once
#include<iostream>
#include<vector>

namespace mz
{
	class bitset
	{
	public:
		bitset(size_t N)
		{//N代表要开多少比特位(简称:位)
			_bits.resize(N / 32 + 1, 0);
			_num = N;
		}

		void set(size_t x)
		{//把第x位设置为1,表示此位置存在

			size_t index = x / 32; //算出映射的位置在第几个整数
			size_t pos = x % 32;   //算出x在整数的第几个位

			_bits[index] |= (1 << pos); //对于这个整数第pos的位置或1
			//1<<pos表示先把1移动到和pos相同位置的比特位上,因为pos=几,1就会左移几位
			//|=表示是或,即除了pos位置的位要变成1,

			++_num;
		}

		void reset(size_t x)
		{//把第x位设置为0,表示此位置不存在

			size_t index = x / 32; //算出映射的位置在第几个整数
			size_t pos = x % 32;   //算出x在整数的第几个位

			_bits[index] &= ~(1 << pos); //对于这个整数第pos的位置与0

			--_num;
		}

		//判断x在不在(也就是说x映射的位是否为1)
		bool test(size_t x)
		{
			size_t index = x / 32; //算出映射的位置在第几个整数
			size_t pos = x % 32;   //算出x在整数的第几个位

			return _bits[index] & (1 << pos); //结果非0为真,0则为假
		}

	private:
		//int* _bits;
		std::vector<int> _bits;
		size_t _num;  //表示存了多少个比特位,映射存储的多少个数据
	};

	void test_bitset()
	{
		bitset bs(100);
		bs.set(99);
		bs.set(98);
		bs.set(97);
		bs.reset(98);

		for (size_t i = 0; i < 100; ++i)
		{
			printf("[%d]:%d\n", i, bs.test(i));
		}
	}

}

部分测试结果如下: 

 


二、布隆过滤器

1、布隆过滤器的提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉 那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用 户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那 些已经存在的记录。 如何快速查找呢?
  • 1. 哈希表存储用户记录,缺点:浪费空间
  • 2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理
  • 3. 哈希与位图结合,即布隆过滤器
布隆过滤器是 由布隆( Burton Howard Bloom )在 1970 年提出的 一种紧凑型的、比较巧妙的 率型数据结构 ,特点是 高效地插入和查询,可以用来告诉你 某样东西一定不存在或者可能存 ,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式 不仅可以提升查询效率,也 可以节省大量的内存空间


2、布隆过滤器的实现

①、基本结构

 一般布隆过滤器存的是字符串或结构体等,一般不会是整形,因为整形就用位图来存了

布隆过滤器底层就是用位图实现的,因为字符串比较常用,故我们直接把字符串作为默认模板参数,其他三个Hash模板参数表示的是用三个位置来映射一个值

构造函数开多少个空间?

有大佬已经计算过了,10个元素就需要长度为43位,那我们干脆初始开个5倍

template<class K = string, class Hash1 = HashStr1, class Hash2 = HashStr2, class Hash3 = HashStr3>
class bloomfilter
{
public:
    //直接上来开满会有问题,因为可能我本身可能就没映射几个值
	//那就根据你大概会存多少个数据,来对应开空间
	//到底开多少比较好有人算过,即你存多少个值就要映射到多少个位
	bloomfilter(size_t num)
		:_bs(5 * num)
		,_N(5 * num)
	{}

private:
	bitset _bs;	//底层是一个位图
	size_t _N;
};

②、三个Hash仿函数实现

 因为要用三个映射位置来映射一个值,故要写三个字符串转为整形的函数,而又因为string类型比较常用,这三个仿函数会作为默认模板参数

下面是能降低哈希冲突的字符串算法(我们下面三个仿函数就选用下面三个算法):

struct HashStr1
{
	size_t operator()(const string& str)
	{	//运用BKDRHash
		size_t hash = 0;
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= 131;
			hash += str[i];
		}

		return hash;
	}
};

struct HashStr2
{
	size_t operator()(const string& str)
	{	//运用RSHash
		size_t hash = 0;
		size_t magic = 63689; //魔数
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= magic;
			hash += str[i];
			magic *= 378551;
		}

		return hash;
	}
};

struct HashStr3
{
	size_t operator()(const string& str)
	{	//运用SDBMHash
		size_t hash = 0;
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= 65599;
			hash += str[i];
		}

		return hash;
	}
};

③、 set

函数是想把key这个数标志为存在,而我们说了要用三个位置来映射这个key值,则调用Hash仿函数先计算出字符串的映射位置,%_N是因为我们刚开始给位图就开了_N个比特位,你算出的映射位置很可能大于_N,故都%_N,既能存储也能统一,则一个key值就能用三个映射位置来表示

注:Hash1是仿函数类型,Hash1()是仿函数对象,当然你也可以写为Hash1 hs1; hs1(key) % _N;但是明显更麻烦

void set(const K& key)
{
	size_t index1 = Hash1()(key) % _N;//利用Hash1类型的匿名对象
	size_t index2 = Hash2()(key) % _N;
	size_t index3 = Hash3()(key) % _N;

	_bs.set(index1);//表示index1这个位置存在
	_bs.set(index2);
	_bs.set(index3);
}

④、 test

功能:判断key值存不存在

因为一个值用了三个映射位置,故我们判断计算出key的三个映射位置在位图中是否同时存在,同时存在key值才存在,反之有一个不存在就肯定不存在

bool test(const K& key)
{
	size_t index1 = Hash1()(key) % _N;
	if (_bs.test(index1) == false)
		return false;

	size_t index2 = Hash2()(key) % _N;
	if (_bs.test(index2) == false)
		return false;

	size_t index3 = Hash3()(key) % _N;
	if (_bs.test(index3) == false)
		return false;

	return true;  //但这里也不一定是真的在,还是可能存在误判

	//判断在,是不准确的,可能存在误判
	//判断不在,是准确的
}

⑤、 删除

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

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给 k个计 数器 (k 个哈希函数计算出的哈希地址 ) 加一,删除元素时,给 k个计数器减一,通过多占用几倍存储 空间的代价来增加删除操作。
缺陷:
1. 无法确认元素是否真正在布隆过滤器中
2. 存在计数回绕

void reset(const K& key)	
{
	//将映射的位置给置0就可以?
	//不支持删除,可能会存在误删,故布隆过滤器一般不支持删除
}

 ⑥、完整代码及测试

#pragma once
#include"bitset.h"
#include<string>

using std::string;
using std::cout;
using std::endl;

namespace mz
{
	struct HashStr1
	{
		size_t operator()(const string& str)
		{	//运用BKDRHash
			size_t hash = 0;
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= 131;
				hash += str[i];
			}

			return hash;
		}
	};

	struct HashStr2
	{
		size_t operator()(const string& str)
		{	//运用RSHash
			size_t hash = 0;
			size_t magic = 63689; //魔数
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= magic;
				hash += str[i];
				magic *= 378551;
			}

			return hash;
		}
	};

	struct HashStr3
	{
		size_t operator()(const string& str)
		{	//运用SDBMHash
			size_t hash = 0;
			for (size_t i = 0; i < str.size(); ++i)
			{
				hash *= 65599;
				hash += str[i];
			}

			return hash;
		}
	};

	template<class K = string, class Hash1 = HashStr1, class Hash2 = HashStr2, class Hash3 = HashStr3>
	class bloomfilter
	{
	public:
		//直接上来开满会有问题,因为可能我本身可能就没映射几个值
		//那就根据你大概会存多少个数据,来对应开空间
		//到底开多少比较好有人算过,即你存多少个值就要映射到多少个位
		bloomfilter(size_t num)
			:_bs(5 * num)
			,_N(5 * num)
		{}

		void set(const K& key)
		{
			size_t index1 = Hash1()(key) % _N;//利用Hash1类型的匿名对象
			size_t index2 = Hash2()(key) % _N;
			size_t index3 = Hash3()(key) % _N;

			_bs.set(index1);
			_bs.set(index2);
			_bs.set(index3);

		}

		bool test(const K& key)
		{
			size_t index1 = Hash1()(key) % _N;
			if (_bs.test(index1) == false)
				return false;

			size_t index2 = Hash2()(key) % _N;
			if (_bs.test(index2) == false)
				return false;

			size_t index3 = Hash3()(key) % _N;
			if (_bs.test(index3) == false)
				return false;

			return true;  //但这里也不一定是真的在,还是可能存在误判
		
			//判断在,是不准确的,可能存在误判
			//判断不在,是准确的
		}

		void reset(const K& key)
		{
			//将映射的位置给置0就可以?
			//不支持删除,可能会存在误删,故布隆过滤器一般不支持删除
		}

	private:
		bitset _bs;	//底层是一个位图
		size_t _N;
	};

	void test_bloomfilter()
	{
		bloomfilter<string> bf(100); //这里不给string,直接用<>也行,因为string是默认的
		bf.set("abcd");
		bf.set("aadd");
		bf.set("bcad");

		cout << bf.test("abcd") << endl;
		cout << bf.test("aadd") << endl;
		cout << bf.test("bcad") << endl;
		cout << bf.test("cbad") << endl;

	}
}


⑦、 优缺点

布隆过滤器优点
  • 1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无
  • 2. 哈希函数相互之间没有关系,方便硬件并行运算
  • 3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  • 4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  • 5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  • 6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
布隆过滤器缺陷
  • 1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再
  • 建立一个白名单,存储可能会误判的数据)
  • 2. 不能获取元素本身
  • 3. 一般情况下不能从布隆过滤器中删除元素
  • 4. 如果采用计数方式删除,可能会存在计数回绕问题

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

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

相关文章

初识测开/测试

前言 在进入软件测试的正式讲解之前&#xff0c;我们需要对这个行业有一个整体的了解。 当我们从软件开发转向软件测试的时候&#xff0c;多数公司是欢迎的&#xff0c;而且难度也小。 反之&#xff0c;当我们从软件测试转向软件开发的时候&#xff0c;难度将会变得很大。 关于…

《持续交付:发布可靠软件的系统方法》- 读书笔记(七)

持续交付&#xff1a;发布可靠软件的系统方法&#xff08;七&#xff09; 第 7 章 提交阶段7.1 引言7.2 提交阶段的原则和实践7.2.1 提供快速有用的反馈7.2.2 何时令提交阶段失败7.2.3 精心对待提交阶段7.2.4 让开发人员也拥有所有权7.2.5 在超大项目团队中指定一个构建负责人 …

Mock测试详细教程入门这一篇就够了!

1、什么是mock测试 1.png Mock测试就是在测试活动中&#xff0c;对于某些不容易构造或者不容易获取的比较复杂的数据/场景&#xff0c;用一个虚拟的对象(Mock对象)来创建用于测试的测试方法。 2、为什么要进行Mock测试 Mock是为了解决不同的单元之间由于耦合而难于开发、测试…

01-React入门

React概述 react是什么&#xff1f; React用于构建用户界面的JS库。是一个将数据渲染为HTML视图的开源JS库。 为什么学&#xff1f; 1.原生JS操作DOM繁琐&#xff0c;效率低 2.使用JS直接操作DOM,浏览器会进行大量的重绘重排 3.原生JS没有组件化编码方案&#xff0c;代码…

【GA-LSSVM预测】基于遗传算法优化最小二乘支持向量机的回归预测(MATLAB代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

数字化转型如何影响企业全要素生产率(2007-2022年)

参照赵宸宇等&#xff08;2021&#xff09;的做法&#xff0c;本团队对来自财贸经济《数字化转型如何影响企业全要素生产率》一文中的基准回归部分进行复刻 一、数据介绍 数据名称&#xff1a;数字化转型如何影响企业全要素生产率 数据范围&#xff1a;上市公司 数据年份&am…

NSSCTF第8页(2)

[LitCTF 2023]就当无事发生 没有环境 [MoeCTF 2022]ezhtml 在js代码找到了flag [HNCTF 2022 WEEK2]ez_ssrf 题目描述说让我们访问index.php,看到了代码 传入的data会base64解码&#xff0c;传参port和host&#xff0c;还有一个没见过的函数fsockopen 查了一下 fsockopen函…

驱动开发LED灯绑定设备文件

头文件 #ifndef __HEAD_H__ #define __HEAD_H__typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t;#define PHY_LED1_ADDR 0x50006000 #define PHY_LED2_ADDR 0x50007000 #defin…

计算机算法分析与设计(18)---回溯法(介绍、子集和问题C++代码)

文章目录 一、回溯法介绍二、子集和问题2.1 知识概述2.2 代码编写 一、回溯法介绍 1. 回溯法&#xff08;back tracking&#xff09;是一种选优搜索法&#xff0c;又称为试探法&#xff0c;有“通用的解题法”之称&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探…

vue3 v-model的使用

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;vue3 v-model的使用 目录 前言 什么是v-model&#xff1f; 基本的v-model用法 自定义组件中的v-model 前言 当涉及到Vue.js 3的前端开发时&#xff0c;v-model是一个不可或缺的工具&#xff0c;它…

从REST到GraphQL:升级你的Apollo体验

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

CPU寄存器与寻址方式

CPU寄存器与寻址方式 1 CPU寄存器2 表达数据位置3 寻址方式3.1 直接寻址3.2 寄存器间接寻址3.3 寄存器相对寻址3.4 基址变址寻址3.5 相对基址变址寻址3.6 按比例变址寻址&#xff08;SIB&#xff09; 本文属于《 X86指令基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 C…

CDN+Nginx反向代理来隐藏c2地址

思路&#xff1a;通过借助CDN和Nginx反向代理和HTTPS来隐藏真实c2服务器Nginx反向代理&#xff1a;通过Nginx对外部流量转发到本地&#xff0c;再设置防火墙只允许localhost访问cs端口达到IP白名单的效果 准备 在这个实验环境中&#xff0c;我们需要准备服务器两台(一台服务端…

1978-2021年全国各省有效灌溉面积数据

1978-2021年全国各省有效灌溉面积数据 1、时间&#xff1a;1978-2021年 2、来源&#xff1a;农业统计NJ、各省NJ、国家统计J 3、指标&#xff1a;有效灌溉面积 4、范围&#xff1a;31省市 5、指标解释&#xff1a; 有效灌溉面积指具有一定的水源&#xff0c;地块比较平整…

爽,我终于实现了selenium图片滑块验证码!【附代码】

因为种种原因没能实现愿景的目标&#xff0c;在这里记录一下中间结果&#xff0c;也算是一个收场吧。这篇文章主要是用selenium解决滑块验证码的个别案列。 思路&#xff1a; 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方&#x…

驱动day4作业

通过字符设备驱动的分步实现编写LED驱动&#xff0c;另外实现特备文件和设备的绑定 head.h #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }…

2007-2022 年上市公司国内外专利授权情况数据

2007-2022 年上市公司国内外专利授权情况 1、来源&#xff1a;国家知识产权局 2、时间&#xff1a;2007-2022 年 3、范围&#xff1a;上市公司 4、指标&#xff1a; 证券代码、年份、省份、城市、行业代码、授权地区、申请类型、专利、发明专利、实用新型、外观设计 5、…

SpringBoot读取Resource下文件的几种方式读取jar里的excel,文件损坏

在项目中涉及到Excle的导入功能&#xff0c;通常是我们定义完模板供用户下载&#xff0c;用户按照模板填写完后上传&#xff1b; 这里待下载模板位置为resource/template/员工基础信息导入模板.xlsx&#xff0c; 分别尝试了四种读取方式&#xff0c;并且测试了四种读取方式分别…

Multi-task Classification Model Based On Multi-modal Glioma Data

框架 体会 为什么不对三个任务用一个CNN呢&#xff1f; 作者未公布代码