【C++历练之路】哈希思想的应用——位图、布隆过滤器

news2024/11/24 13:05:53

W...Y的主页 😊

代码仓库分享💕 


前言:我们使用hash思想学习了哈希表,进行了模拟实现unordered_set与unordered_map。这些都是用hash思想实现出来的数据结构,今天我们来学习一下hash的应用——位图、布隆过滤器。

目录

1. 哈希的应用

1.1 位图

1.1.1 位图概念

1.1.2 位图的实现

1.1.3 位图的应用

1.1.4变形应用

1.2 布隆过滤器

1.2.1 布隆过滤器提出

1.2.2布隆过滤器概念

1.2.3 布隆过滤器的插入

​编辑 

1.2.4 布隆过滤器的查找

 1.2.5 布隆过滤器删除

1.2.6 布隆过滤器优点

1.2.7 布隆过滤器缺陷


1. 哈希的应用

1.1 位图

1.1.1 位图概念

1. 面试题
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
这40亿个数中。【腾讯】

 我们从脑海中第一冒出来的想法是什么呢?

1. 遍历,时间复杂度O(N)
2. 排序(O(NlogN)),利用二分查找: logN

3.set+find函数
4. 位图解决

前三种方法的唯一缺陷是40亿个数据太大,无法进行存储。(1G大概有10亿字节,10亿大概可以存储2.5亿个无符号整数,40亿大概需要16G内存。)

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一
个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。比如: 

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

1.1.2 位图的实现

根据上述问题,我们需要开一个类数组的东西,需要开多大呢?无符号整数的范围是0~2^32-1,所以我们需要开2^32次方的比特位。大概需要512M的内存即可表式无符号整数是否存在。C++中提供位图bitset,我们自己进行模拟实现一下。

首先我们使用非类型模板参数,进行模板化。位图是存在数组中的,但是我们不能以比特位去开数组,所以我们可以使用bool、char、int进行定义。(以下代码是以int为单位开的数组,所以我们得除以32,向上取整+1)。

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N / 32 + 1, 0);
		//cout << N << endl;
	}

	// 把x映射的位标记成1
	void set(size_t x)
	{
		assert(x <= N);

		size_t i = x / 32;
		size_t j = x % 32;

		_bits[i] |= (1 << j);
	}

	// 把x映射的位标记成1
	void reset(size_t x)
	{
		assert(x <= N);

		size_t i = x / 32;
		size_t j = x % 32;

		_bits[i] &= ~(1 << j);
	}

	bool test(size_t x)
	{
		assert(x <= N);

		size_t i = x / 32;
		size_t j = x % 32;

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

 当我们进行一一映射时,首先将数除以32来找到对应的哪个int中,在将此数模32算出在此int的具体位置,那个位置就是我们需要操作的地方。

set函数是将x数从0变1的函数,所以我们找到此位置后将1左移j位,然后进行或操作即可。

reset函数是将数从1变0的函数,所以我们先左移后再取反,最后进行与操作即可。

test函数是查找数是否出现过,我们直接返回左移j位再进行与操作的结果。

1.1.3 位图的应用

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

1.1.4变形应用

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

使用map肯定是存不下这么多的数的,但是位图的功能是判断一个数是否出现。我们应该可以自己手撕一个位图进行,用两个字节存储信息:00代表没有出现过,01代表出现过一次,10代表出现两次以上。

我们也可以使用两个位图进行操作,和上面的逻辑一样,但是我们不用手撕,两个位图进行复用配合即可。

template<size_t N>
class two_bit_set
{
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);
		}
	}


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

		return false;
	}
private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

1.2 布隆过滤器

1.2.1 布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉
那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用
户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那
些已经存在的记录。 如何快速查找呢?

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

1.2.2布隆过滤器概念

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

如何选择哈希函数个数和布隆过滤器长度icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/43263751/ 

1.2.3 布隆过滤器的插入

 

向布隆过滤器中插入:"baidu"

 

作为字符串,我们必须将字符串先转换成整数再转换成映射位置进行存储,但是数组是无穷无尽的而整数是有限的,所以为了避免hash冲突,我们必须映射多组来减少哈希冲突。 

首先我们得需要三个hash函数来将整数再转换成三个不同映射位置。字符串hash算法icon-default.png?t=N7T8http://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

struct HashFuncBKDR
{
	// BKDR
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

struct HashFuncAP
{
	// AP
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) // 偶数位字符
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else              // 奇数位字符
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
			}
		}

		return hash;
	}
};

struct HashFuncDJB
{
	// DJB
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};

 布隆过滤器:

template<size_t N,
	class K = string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;

		_bs->set(hash1);
		_bs->set(hash2);
		_bs->set(hash3);
	}

	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		if (_bs->test(hash1) == false)
			return false;

		size_t hash2 = Hash2()(key) % M;
		if (_bs->test(hash2) == false)
			return false;

		size_t hash3 = Hash3()(key) % M;
		if (_bs->test(hash3) == false)
			return false;

		return true; // 存在误判(有可能3个位都是跟别人冲突的,所以误判)
	}

private:
	static const size_t M = 10 * N;
	std::bitset<M>* _bs = new std::bitset<M>;
};

1.2.4 布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特
位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为
零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可
能存在,因为有些哈希函数存在一定的误判。

比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其
他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。 

 1.2.5 布隆过滤器删除

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

比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也
被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计
数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储
空间的代价来增加删除操作。

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

1.2.6 布隆过滤器优点

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

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

1.2.7 布隆过滤器缺陷

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

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

我们可以结合计数来进行,如果一个位置有两个标记则记为2,删除时让计数-1即可,等到减到0时删除即可。

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

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

相关文章

课题组里有一个卷王是什么体验?

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

Ansys ACT的一个例子

由XML和IronPython文件组成&#xff0c;文件结构如下&#xff1a; ExtSample.xml <extension version"1" name"ExtSample1"><guid shortid"ExtSample1">2cc739d5-9011-400f-ab31-a59e36e5c595</guid><script src"sam…

【全开源】废品回收微信小程序基于FastAdmin+ThinkPHP+UniApp

介绍 一款基于FastAdminThinkPHPUniApp开发的废品回收系统&#xff0c;适用废品回收站、再生资源回收公司上门回收使用的小程序 功能特性 1、会员注册 支持小程序授权注册和手机号注册 2、回收品类 可设置回收品类&#xff0c;废纸、废金属、废玻璃、旧衣服等 3、今日指导价…

如何快速提取出一个文件里面全部指定类型的文件的全部路径

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 打开工具&#xff0c;切换到第五个模块&#xff0c;文件批量复制模块&#xff08;快捷键&#xff1a;Ctrl5&#xff09; 点击右边的“搜索添加”按钮&#…

[muduo网络库]——muduo库三大核心组件之Channel类(剖析muduo网络库核心部分、设计思想)

接着上文[muduo网络库]——muduo库的Reactor模型&#xff08;剖析muduo网络库核心部分、设计思想&#xff09;&#xff0c;接下来详细介绍一下这三大核心组件中的Channel类。 先回顾一下三大核心组件之间的关系。 接着我们进入正题。 Channel Channel类封装了一个 fd 、fd感兴…

onnx模型截取部分

这个是有需求的&#xff0c;比如有多个输入节点&#xff0c;我只用其中几个&#xff0c;或有多个输出节点&#xff0c;我只用其中几个。 比如这个输入&#xff0c;我们可以直接把transpose去掉&#xff0c;用类pytorch的N,C,H,W的格式输入。 还有如下输出&#xff1a; tran…

灵活QinQ

拓扑图 配置 sysname AR1 # interface GigabitEthernet0/0/0.10dot1q termination vid 10ip address 12.1.1.1 255.255.255.0 arp broadcast enable # interface GigabitEthernet0/0/0.20dot1q termination vid 20ip address 21.1.1.1 255.255.255.0 arp broadcast enable # …

LeetCode 题目 120:三角形最小路径和

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

Java--初识类和对象

前言 本篇讲解Java类和对象的入门版本。 学习目的&#xff1a; 1.理解什么是类和对象。 2.引入面向对象程序设计的概念 3.学会如何定义类和创建对象。 4.理解this引用。 5.了解构造方法的概念并学会使用 考虑到篇幅过长问题&#xff0c;作者决定分多次发布。 面向对象的引入 J…

《QT实用小工具·六十三》QT实现微动背景,界面看似静态实则动态

1、概述 源码放在文章末尾 该项目实现了微动背景&#xff0c;界面看似静态实则动态&#xff0c;风动&#xff0c;幡动&#xff0c;仁者心动&#xff0c;所以到底是什么在动&#xff1f;哈哈~ 界面会偷偷一点一点改动文字颜色的颜色填充。 虽然是动态&#xff0c;但是慢到难以…

羊大师解析,如何遵守《节约用水条例》

羊大师解析&#xff0c;如何遵守《节约用水条例》 《节约用水条例》的实施旨在倡导和推动全社会形成节约用水的良好风尚&#xff0c;以应对日益严峻的水资源短缺问题。羊大师发现随着这一条例的深入实施&#xff0c;越来越多的人开始意识到节约用水的重要性&#xff0c;并积极…

HTML5实现简洁好看的个人主页,个人小站(多种风格附源码)

文章目录 1.烟灰主题个人主页1.1 个人主页界面1.2 个人信息界面1.3 兴趣爱好界面1.4 个人作品界面 2.紫霞主题个人主页2.1 个人主页界面2.2 个人信息界面2.3 兴趣爱好界面2.4 个人作品界面 3.墨夜主题个人主页3.1 个人主页界面3.2 个人信息界面3.3 兴趣爱好界面3.4 个人作品界面…

《小猫咪大城市》 48小时销量破40万套,一匹休闲游戏黑马诞生

易采游戏网5月13日消息&#xff0c;近日一款名为《小猫咪大城市》的游戏在Steam、Switch和Xbox平台上正式发售&#xff0c;凭借其独特的游戏设定和可爱的猫咪角色&#xff0c;迅速赢得了玩家们的喜爱。据官方宣布&#xff0c;游戏在发售后的短短48小时内&#xff0c;销量已经突…

汽车灯罩材料使用PMMA(亚克力)具有哪些优势?汽车车灯的灯罩如果破损破裂破洞了要怎么修复?

汽车灯罩材料使用PMMA&#xff08;亚克力&#xff09;具有哪些优势 首先&#xff0c;PMMA具有高透明度&#xff0c;其透光率可达92%以上&#xff0c;使得光线能够均匀、清晰地透过灯罩&#xff0c;为驾驶者提供明亮且均匀的照明效果&#xff0c;确保行车安全。 其次&#xff…

JavaEE初阶-多线程进阶1

文章目录 前言一、常见的锁策略1.1 乐观锁与悲观锁1.2 重量级锁与轻量级锁1.3 自旋锁与挂起等待锁1.4 可重入锁与不可重入锁1.5 公平锁与非公平锁1.6 互斥锁与读写锁 二、synchronized的优化策略2.1 锁升级2.2 锁消除2.3 锁粗化 前言 多线程进阶的内容在面试中容易考&#xff…

解决SpringBoot整合MyBatis和MyBatis-Plus,请求后不打印sql日志

问题发现 在整合springBootmyBatis时&#xff0c;发现请求不打印sql日志&#xff0c;示例代码如下&#xff1a; RestController public class MyController {AutowiredProductMapper productMapper;GetMapping("/test")public void test() {System.out.println(&qu…

【知识碎片】2024_05_12

本篇记录了两个代码题【字符个数的统计】和【多数元素】(下有一段快排的代码)&#xff0c;以及两个关于数组的选择题。 每日代码 字符个数统计 字符个数统计_牛客题霸_牛客网 统计ascall码在0~127内的字符出现过几次&#xff08;重复出不再计算&#xff09; #include <st…

快速入门:利用Go语言下载Amazon商品信息的步骤详解

概述 在这篇文章中&#xff0c;我们将深入探讨如何利用Go语言这一强大的工具&#xff0c;结合代理IP技术和多线程技术&#xff0c;实现高效下载Amazon的商品信息。首先&#xff0c;让我们来看看为什么选择Go语言作为开发网络爬虫的首选语言。 Go语言在网络开发中的特点 简洁…

Redis五大基本数据类型介绍及其使用场景

文章目录 1 String&#xff08;字符串&#xff09;应用场景 2 List&#xff08;列表&#xff09;应用场景 3 Set&#xff08;集合&#xff09;4 sorted set&#xff08;有序集合&#xff09;应用场景 5 hash&#xff08;哈希&#xff09;应用场景 Redis 是一个开源&#xff0c;…

Spring Boot集成zookeeper快速入门Demo

1.什么是zookeeper&#xff1f; Zookeeper 是一个开源的分布式协调服务&#xff0c;目前由 Apache 进行维护。Zookeeper 可以用于实现分布式系统中常见的发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。它具有以下特性…