【C++进阶】十一、哈希的应用---布隆过滤器(二)

目录

一、布隆过滤器提出

二、布隆过滤器概念

三、布隆过滤器实现

3.1 布隆过滤器的插入

3.2 布隆过滤器的查找

3.3 布隆过滤器的删除

3.4 完整代码

四、布隆过滤器优点

五、布隆过滤器缺陷


一、布隆过滤器提出

       在注册账号设置昵称的时候,有些软件要求每个用户昵称要保持唯一性,系统必须检测你输入的昵称是否被使用过,这本质就是一个K的模型,只需要判断这个昵称存在还是不存在

  1. 用哈希表存储用户昵称,缺点:浪费空间
  2. 用位图存储用户昵称,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了
  3. 将哈希与位图结合,即布隆过滤器

为什么说位图处理不了字符串??

        位图虽然能够大大节省内存空间,但由于字符串的组合形式太多了,一个字符的取值有256种,而一个数字的取值只有10种,字符串的数量是远远大于整数的,使用位图就会出现一个整数对应多个字符串的情况,即哈希冲突,这种冲突概率是很高的(如,某个昵称明明就是没有使用过,系统却判断使用过了)

而布隆过滤器可以把这些哈希冲突大大降低

二、布隆过滤器概念

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

布隆过滤器实际上就是位图的变形、拓展

先说一下位图的误判

  1. 位图判断不在,是准确的 
  2. 位图判断在,是不准确的,因为可能本来不在,但是这个位置跟别人冲突,出现误判,对于字符串这种误判率会很高

布隆过滤器可以大大降低这些哈希冲突

假设布隆过滤器使用三个哈希函数进行映射(位图只是使用一个哈希函数),每个字符串就会映射三个比特位,那么“find”在位图中会有三个比特位会被置1,就算前两个哈希函数计算出来的位置都产生了冲突(前两个比特位为1),但由于第三个哈希函数计算出的比特位的为0(第三个比特位为0),此时就会判断这个“find”不存在,这种误判概率大大降低了

但随着位图中添加的数据不断增多,位图中1的个数也在不断增多,此时就会导致误判的概率增加

布隆过滤器的特点:

  • 当布隆过滤器判断一个数据存在可能是不准确的,因为这个数据对应的比特位可能被其他一个数据或多个数据占用了
  • 当布隆过滤器判断一个数据不存在是准确的,因为如果该数据存在那么该数据对应的比特位都应该已经被设置为1了

 如何控制误判率??

  1. 过小的布隆过滤器很快所有的比特位都会被设置为1,此时布隆过滤器的误判率就会变得很高,因此布隆过滤器的长度会直接影响误判率,布隆过滤器的长度越长其误判率越小
  2. 此外,哈希函数的个数也需要权衡,哈希函数的个数越多布隆过滤器中比特位被设置为1的速度越快,并且布隆过滤器的效率越低,但如果哈希函数的个数太少,也会导致误判率变高。

如何选择哈希函数的个数和布隆过滤器的长度??

有大佬通过计算得出了公式,博客链接:详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)icon-default.png?t=N176https://zhuanlan.zhihu.com/p/43263751/

 

公式为:

其中k为哈希函数个数,m为布隆过滤器长度,n为插入的元素个数,p为误判率

直接使用第二个公式,这里可以大概估算一下:

  • 如果使用3个哈希函数,即 k的值为3,ln2 的值我们取0.7,那么  m = 3*n/0.7 = 4.2n 左右,也就是布隆过滤器的长度应该是插入元素个数的4倍左右
  • 如果使用4个哈希函数,即 k的值为4,ln2 的值我们取0.7,那么  m = 4*n/0.7 = 5.7n 左右,也就是布隆过滤器的长度应该是插入元素个数的6倍左右

注:布隆过滤器在STL中并没有实现,因为需求不一样,即所需哈希哈数个数不同,官方库就没有给出,有需要需要自己实现

三、布隆过滤器实现

布隆过滤器实现直接复用STL的bitset,bitset符合我们的需求,封装一下即可

size_t N 是位图长度,size_t X 是每个元素映射时建议的位图大小,布隆过滤器可以实现为一个模板类,因为插入布隆过滤器的元素不仅仅是字符串,也可以是其他类型的数据,只有调用者能够提供对应的哈希函数将该类型的数据转换成整型即可,但一般情况下布隆过滤器都是用来处理字符串的,所以这里可以将模板参数K的缺省类型设置为string

template<size_t N,//位图长度,N*X需要开的空间大小
		size_t X = 4,//使用3个哈希函数,布隆过滤器的长度应该是插入元素个数的4倍左右
		class K = string,
		class HashFunc1 = BKDRHash,
		class HashFunc2  = APHash,
		class HashFunc3 = DJBHash
		//有需要再增加哈希函数
	>

基本框架如下:

template<size_t N,//位图长度,N*X需要开的空间大小
	size_t X = 4,//使用3个哈希函数,布隆过滤器的长度应该是插入元素个数的4倍左右
	class K = string,
	class HashFunc1 = BKDRHash,
	class HashFunc2 = APHash,
	class HashFunc3 = DJBHash
	//有需要再增加哈希函数
>

class BloomFilter
{
public:

private:
	std::bitset<N* X> _bs;
};

 这里还需增加字符串转换成整型的哈希函数,字符串哈希算法博客:各种字符串Hash函数 - clq - 博客园 (cnblogs.com)icon-default.png?t=N176https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

这里选取了经过测试后综合评分最高的BKDRHash、APHash和DJBHash哈希函数,这三种哈希算法在多种场景下产生哈希冲突的概率是最小的 

三个哈希函数代码如下 :

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

struct APHash
{
	size_t operator()(const string& key)
	{
		unsigned int hash = 0;
		int i = 0;

		for (auto ch : key)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
			}

			++i;
		}

		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& key)
	{
		unsigned int hash = 5381;

		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};

3.1 布隆过滤器的插入

插入元素时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后将位图中的这三个比特位设置为1即可,设置直接调用库里的即可

void set(const K& key)
{
    //计算出key对应的三个位
	size_t hash1 = HashFunc1()(key) % (N * X);
	size_t hash2 = HashFunc2()(key) % (N * X);
	size_t hash3 = HashFunc3()(key) % (N * X);
    //设置为1
	_bs.set(hash1);
	_bs.set(hash2);
	_bs.set(hash3);
}

3.2 布隆过滤器的查找

需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后判断位图中的这三个比特位是否被设置为1即可,直接调用 bitset 的 test函数

  1. 只要这三个比特位当中有一个比特位未被设置则说明该元素一定不存在
  2. 如果这三个比特位全部被设置,则返回true表示该元素存在(可能存在误判)
bool test(const K& key)
{
	size_t hash1 = HashFunc1()(key) % (N * X);
	if (!_bs.test(hash1))
	{
		return false;
	}

	size_t hash2 = HashFunc2()(key) % (N * X);
	if (!_bs.test(hash2))
	{
		return false;
	}

	size_t hash3 = HashFunc3()(key) % (N * X);
	if (!_bs.test(hash3))
	{
		return false;
	}
	// 前面判断不在都是准确,不存在误判

	return true; // 可能存在误判,映射几个位置都冲突,就会误判
}

3.3 布隆过滤器的删除

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

  • 布隆过滤器判断一个元素存在时可能存在误判,因此无法保证要删除的元素确实在布隆过滤器当中,此时将位图中对应的比特位清0会影响其他元素
  • 此外,就算要删除的元素确实在布隆过滤器当中,也可能该元素映射的多个比特位当中有些比特位是与其他元素共用的,此时将这些比特位清0也会影响其他元素

一种支持删除的方法:

将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:

  1. 无法确认元素是否真正在布隆过滤器中
  2. 存在计数回绕

布隆过滤器最终还是没有提供删除的接口,因为使用布隆过滤器本来就是要节省空间和提高效率的。在删除时需要遍历文件或磁盘中确认待删除元素确实存在,而文件IO和磁盘IO的速度相对内存来说是很慢的,并且为位图中的每个比特位额外设置一个计数器,就需要多用原位图几倍的存储空间,这个代价也是不小的

3.4 完整代码

#pragma once

#include <bitset>
#include <string>

namespace fy
{
	struct BKDRHash
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto ch : key)
			{
				hash *= 131;
				hash += ch;
			}
			return hash;
		}
	};

	struct APHash
	{
		size_t operator()(const string& key)
		{
			unsigned int hash = 0;
			int i = 0;

			for (auto ch : key)
			{
				if ((i & 1) == 0)
				{
					hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
				}
				else
				{
					hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
				}

				++i;
			}

			return hash;
		}
	};

	struct DJBHash
	{
		size_t operator()(const string& key)
		{
			unsigned int hash = 5381;

			for (auto ch : key)
			{
				hash += (hash << 5) + ch;
			}

			return hash;
		}
	};

	// 假设N是最多存储的数据个数
	// 平均存储一个值,开辟X个位
	template<size_t N,//位图长度,N*X需要开的空间大小
		size_t X = 4,//使用3个哈希函数,布隆过滤器的长度应该是插入元素个数的4倍左右
		class K = string,
		class HashFunc1 = BKDRHash,
		class HashFunc2  = APHash,
		class HashFunc3 = DJBHash
		//有需要再增加哈希函数
	>

	class BloomFilter
	{
	public:
		void set(const K& key)
		{
			//计算出key对应的三个位的哈希地址
			size_t hash1 = HashFunc1()(key) % (N * X);
			size_t hash2 = HashFunc2()(key) % (N * X);
			size_t hash3 = HashFunc3()(key) % (N * X);
			//设置为1
			_bs.set(hash1);
			_bs.set(hash2);
			_bs.set(hash3);
		}

		bool test(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % (N * X);
			if (!_bs.test(hash1))
			{
				return false;
			}

			size_t hash2 = HashFunc2()(key) % (N * X);
			if (!_bs.test(hash2))
			{
				return false;
			}

			size_t hash3 = HashFunc3()(key) % (N * X);
			if (!_bs.test(hash3))
			{
				return false;
			}
			// 前面判断不在都是准确,不存在误判

			return true; // 可能存在误判,映射几个位置都冲突,就会误判
		}

	private:
		std::bitset<N* X> _bs;
	};
	
	void Test_BloomFilter()
	{
		srand(time(0));
		const size_t N = 100000;
		BloomFilter<N> bf;

		std::vector<std::string> v1;
		std::string url = "https://blog.csdn.net/m0_64280701/article/details/129699384?spm=1001.2014.3001.5501";

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

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

		// v2跟v1是相似字符串集,但是不一样
		std::vector<std::string> v2;
		for (size_t i = 0; i < N; ++i)
		{
			std::string url = "https://blog.csdn.net/m0_64280701/article/details/129699384?spm=1001.2014.3001.5501";
			url += std::to_string(999999 + 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(i + rand());
			v3.push_back(url);
		}

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

四、布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

五、布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Element table组件内容\n换行解决办法

项目使用<el-table>组件 <el-table :data"warnings" :row-class-name"highlightRow" v-loading"isLoading"> <el-table-column label"ID" prop"id"/> <el-table-column label"时间" pro…

【C++】STL容器、算法的简单认识

几种模板首先认识一下函数模板、类模板、栈模板。函数模板函数模板就是一个模型&#xff0c;而模板函数是函数模板经过类型实例化的函数。如下template<class T>是一个简单的函数模板&#xff1a;template<class T> T Max(T a, T b) {return a > b ? a : b; } …

【CodeForces】Codeforces Round 859 (Div. 4) D

嘿嘿嘿&#xff0c;CF虐我千百遍&#xff0c;我待CF如初见&#xff01; &#xff08;doge&#xff09; 目录 题目含义&#xff1a; 前缀和&#xff1a; 代码 &#x1f386;音乐分享&#xff08;点击链接可以听哦&#xff09; A Hundred Miles&#xff08;一百英里&#xff09;…

看完这篇 教你玩转渗透测试靶机vulnhub——My File Server: 1

Vulnhub靶机My File Server: 1渗透测试详解Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;FTP匿名登入&#xff1a;③&#xff1a;SMB共享服务&#xf…

微软Bing GPT支持AI绘画了,输入文字就能出图

我想要一张图片&#xff1a;大象、珊瑚、火山、云朵我想要一张图片&#xff1a;亚特兰蒂斯&#xff0c;奥利匹克&#xff0c;喜马拉雅山我想要一张图片&#xff1a;洗衣机、长颈鹿、电视、鲸鱼我想要一张蓝色长颈鹿、红色鲸鱼和飘逸的绿色长发的图片我想要一张有趣的Docker标志…

值得记忆的STL常用算法,分分钟摆脱容器调用的困境,以vector为例,其余容器写法类似

STL常用算法 概述&#xff1a; 算法主要是由头文件<algorithm> <functional> <numeric>组成 <algorithm>是所有STL头文件中最大的一个&#xff0c;范围涉及到比较、交换、查找、遍历操作、复制、修改等等 <nuneric>体积很小&#xff0c;只包括…

文心一言 vs GPT-4 —— 全面横向比较

文心一言 vs GPT-4 —— 全面横向比较 3月15日凌晨&#xff0c;OpenAI发布“迄今为止功能最强大的模型”——GPT-4。我第一时间为大家奉上了体验报告《OpenAI 发布GPT-4——全网抢先体验》。 时隔一日&#xff0c;3月16日下午百度发布大语言模型——文心一言。发布会上&#…

Halcon转OpenCV实例--纺织物折痕检测(附源码)

导 读 本文主要介绍Halcon转OpenCV实例--纺织物折痕检测(附源码)。 实例来源 实例来源于《Halcon机器视觉算法原理与编程实战》7.4.2实例 下面测试图片也来源于图书代码,如有侵权请联系删除: 上图肉眼可见的折痕,类似脏污,我们的目的是将折痕检测出来。 Halcon实现 …

防火墙和IDS

文章目录一、结合以下问题对当天内容进行总结1. 防火墙如何处理双通道协议&#xff1f;2&#xff0c;防火墙支持那些NAT技术&#xff0c;主要应用场景是什么&#xff1f;3. 防火墙如何处理NAT&#xff1f;4. 当内网PC通过公网域名解析访问内网服务器时&#xff0c;会存在什么问…

交通信号标志识别软件(Python+YOLOv5深度学习模型+清新界面)

摘要&#xff1a;交通信号标志识别软件用于交通信号标志的检测和识别&#xff0c;利用机器视觉和深度学习智能识别交通标志并可视化记录&#xff0c;以辅助无人驾驶等。本文详细介绍交通信号标志识别软件&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实现代码以及…

传输层协议----UDP/TCP

文章目录前言一、再谈端口号端口号的划分认识知名端口号(Well-Know Port Number)两个问题nestatpidof二、UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项基于UDP的应用层协议二、TCP协议TCP协议段格式可靠性问题确认应答(ACK)机制流量控制六个标志位PSHUG…

经典七大比较排序算法 ·上

经典七大比较排序算法 上1 选择排序1.1 算法思想1.2 代码实现1.3 选择排序特性2 冒泡排序2.1 算法思想2.2 代码实现2.3 冒泡排序特性3 堆排序3.1 堆排序特性&#xff1a;4 快速排序4.1 算法思想4.2 代码实现4.3 快速排序特性5 归并排序5.1 算法思想5.2 代码实现5.3 归并排序特性…

【Zblog建站】搭建属于自己的博客网站,并内网穿透实现公网访问

文章目录1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置4. 公网访问测试5. 结语1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕不开…

Golang每日一练(leetDay0014)

目录 40. 组合总和 II Combination Sum II &#x1f31f;&#x1f31f; 41. 缺失的第一个正数 First Missing Positive &#x1f31f;&#x1f31f;&#x1f31f; 42. 接雨水 Trapping Rain Water &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题…

MySQL主从复制

主从复制概述 如何提升数据库并发能力 在实际工作中&#xff0c;我们常常将 Redis 作为缓存与 MySQL 配合来使用&#xff0c;当有请求的时候&#xff0c;首先会从缓存中进行查找&#xff0c;如果存在就直接取出。如果不存在再访问数据库&#xff0c;这样就 提升了读取的效率&…

【设计模式】23种设计模式之七大原则

【设计模式】23种设计模式之七大原则什么是设计模式的原则1、单一职责原则基本介绍案例分析注意事项2、接口隔离原则基本介绍案例分析代码实现3、依赖倒转原则基本介绍案例分析依赖传递的三种方式注意事项4、里氏替换原则关于继承性的思考和说明基本介绍案例分析5、开闭原则ocp…

Docker6种网络配置详解,网络模式应该这么选

文章目录一、Bridge网络模式二、Host网络模式三、Overlay网络模式四、None网络模式五、Macvlan网络模式六、Ipvlan网络模式七、网络模式选择在Docker中&#xff0c;网络配置是一个重要的主题&#xff0c;因为容器需要与其他容器或外部网络进行通信。Docker提供了多种网络模式和…

6.S081——Lab1——Xv6 and Unix utilities

0. Briefly Speaking 这是记录本人完成MIT 6.S081课程系列博客的第一篇&#xff0c;在实验过程中我发现有非常多的点需要及时记录&#xff0c;也可能会有很多问题在完成后续的实验之后需要补记。这使得写这个系列博客非常有必要性&#xff0c;篇幅也可能会很长&#xff0c;并且…

算法刷题总结 (二) 回溯与深广搜算法

算法总结2 回溯与深广搜算法一、理解回溯算法1.1、回溯的概念1.2、回溯法的效率1.3、回溯法问题分类1.4、回溯法的做题步骤二、经典问题2.1、组合问题2.1.1、77. 组合 - 值不重复2.1.2、216.组合总和III - 值不重复且等于目标值2.1.3、17. 电话号码的字母组合 - 双层回溯2.1.4、…

《网络安全入门到精通》 - 2.1 - Windows基础 - DOS命令Windows防火墙Windows共享文件

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「订阅专栏」&#xff1a;此文章已录入专栏《网络安全入门到精通》 Windows基础一、DOS命令1、目录文件操作dir 列出目录文件cd 切换目录md 创建目录rd 删除目录move 移动文件或目…
最新文章