【C++学习】哈希的应用—位图与布隆过滤器

news2024/11/19 22:45:17

目录

  • 1.位图
    • 1.1位图的概念
    • 1.2位图的实现
    • 3.位图的应用
  • 2.布隆过滤器
    • 2.1 布隆过滤器提出
    • 2.2布隆过滤器概念
    • 2.3如何选择哈希函数个数和布隆过滤器长度
    • 2.4布隆过滤器的实现
      • 2.4.1布隆过滤器插入操作
      • 2.4.2布隆过滤器查找操作
      • 2.4.3 布隆过滤器删除
    • 2.5 布隆过滤器优点
    • 2.6布隆过滤器缺陷
  • 3.海量数据面试题
    • 3.1 哈希切割
    • 3.2 位图应用
    • 3.3 布隆过滤器

文章简介
在这篇文章中,你会学习到关于哈希思想的最常见的两个应用,也就是 位图布隆过滤器
文章会讲解位图和布隆过滤器的概念,底层实现,对应的适应的场景,以及相关经典 海量数据面试题 及解析。

1.位图

1.1位图的概念

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

比如这道 腾讯 的面试题目:
面试题目:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
解析:
40亿个整型数据所需内存大小:10亿字节约等于1G,那么40亿个整型,就是40亿*4(字节)=160亿字节≈16G。

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

上面的两种做法都是不可行的,因为内存不够。

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

第三种方法,利用位图解决,因为是要在40亿个数中查找,数据的类型是一个整型,范围为0~UINT_MAX。所以我们只需要UINT_MAX个比特位,所需的内存也就是512M,然后将这40亿个整数利用这UINT_MAX个比特位就可以表示他们的存在状态;

图解:
假设有一个整型数组array(如下图),因为里面的数据范围为1~22,所以我们就可以开一个int大小的数组(有32个比特位,可以表示32个不同数的存在状态),映射地址的方法这里采用的是直接定址法;
计算:第i个整型中:i = (该数)/ 32;
该整形中第j个比特位:j = (该数)% 32;
在这里插入图片描述

1.2位图的实现

  1. 因为位图需要整型的连续的空间,所以这里我们用vactor 即可

  2. 所开空间的大小的计算:
    这里开的是一个范围,假如上面的面试题,有40亿个整型数据,因为有40亿个数据,但是不能 只开40亿个比特位的空间,因为如果只开了40亿个比特位的话,就只能表示数据大小为0~40亿的数据,然而数据类型为int,数据最大值超过了40亿,这样超过了40亿的数据就表示不了了。

  3. 因为空间开的大小不一样,所以这里需要利用非类型模板参数

  4. 所开的空间是以整型为单位开辟,所以确认了所需的比特位后,还需计算是多少个int(32个比特位)大小,如果换算为int大小,有余数的话,就应该多开一个int大小


template<size_t N>     //非类型模板参数
class bitset
{
public:
	bitset()
	{
		_bitset.resize(N/32+1, 0);  //所需开的空间,因为空间都只能以整型为单位开,所以需要除以32
	}
	void set(size_t x)       //将x对应的比特位置1
	{
		size_t i = x / 32;    //确定是第几个int
		size_t j = x % 32;    //确定是该int里面的第几个比特位
		_bitset[i] |= (1 << j);    //将1左移j个比特位,在与该位置的数进行 或等操作(如下图有解析)
	}
	void reset(size_t x)     //将x对应的比特位置0
	{
		size_t i = x / 32;
		size_t j = x % 32;
		_bitset[i] &= ~(1 << j);     //将1左移j个比特位,然后取反,再与该位置的数进行 与等操作(如下图有解析)
	}
	bool test(size_t x)     //查找x是否存在
	{
		size_t i = x / 32;
		size_t j = x % 32;
		return _bitset[i] & (1 << j);   //将1左移j个比特位,再与该位置的数进行 与操作(如下图有解析)
	}
private:
	vector<int> _bitset;
};

解析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.位图的应用

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

2.布隆过滤器

2.1 布隆过滤器提出

利用位图只能处理整型数据,但是现实生活中,不只是整型需要进行查找是否存在等问题,还有其他类型,比如:字符串string…
但是如果将字符串转为整型,然后再利用位图处理的话,就会面临一个问题,
就是可能不同的字符串(或则其他类型)转为整型后,利用哈希函数映射的位置相同,这就有可能误判。
分析:误判只有可能将本来不存在的一个字符串(或则其他)误判为存在,因为有可能有一个已经存在的数据与这个字符串转为整型后映射的位置相同。

举个例子:
两个数据 “abcd” 和 “aacc”
如果就按照将数据里面的每个字符相加转为整型再映射,则他们的映射位置会一样,如果“abcd”已经存在了,但是现在要判断“aacc”是否存在,这是就会误判为存在;

2.2布隆过滤器概念

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

布隆过滤器的思想就是:
将一个数据利用不同的哈希函数(假设有X个)映射到多个位置,在位图中进行标记存在,当来了一个数据需要判断存不存在的问题时,就需要将这数据利用这X个哈希函数进行映射到位图上判断在不在,如果这个数经过这了这X个哈希函数映射后,在位图中判断全部都在,那么就判断这个数据存在,如果有一个判断不在,那就不在。

如图:obj1与obj2用3个不同的哈希函数fun1,fun2,fun3映射,在位图进行标记;
在这里插入图片描述

注意:这里判断存在也是存在误判的,只是降低了误判概率。

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

很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。

另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

在这里插入图片描述

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

如何选择适合业务的 k 和 m 值呢,

公式:k = ln2 * ( m / n )

2.4布隆过滤器的实现

2.4.1布隆过滤器插入操作

与位图类似,只是布隆过滤器的插入需要利用多个哈希函数映射多个位置。
如图:
在这里插入图片描述
代码实现:

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

struct Func2
{
	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 Func3
{
	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=Func1, 
	class Hash2=Func2, 
	class Hash3 =Func3>
class bloom
{
public:
    /插入操作
	void set(const K& key)
	{
		size_t i = Hash1()(key) % M;
		size_t j = Hash2()(key) % M;
		size_t z = Hash3()(key) % M;

		_bs.set(i);
		_bs.set(j);
		_bs.set(z);
	}
	/查找操作
	void test()
	{
	/
	}
private:
	static const size_t M = N * 4;
	bitset<M> _bs;
};

2.4.2布隆过滤器查找操作

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

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

代码实现:

bool test(const K& key)
{
		
	size_t i = Hash1()(key) % M;
	bool ret = _bs.test(i);
	if (ret == false)
		return false;

	size_t j = Hash2()(key) % M;
	ret = _bs.test(j);
	if (ret == false)
		return false;

	size_t z = Hash3()(key) % M;
	ret = _bs.test(z);
	if (ret == false)
		return false;

	return true;
}

2.4.3 布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。因为不同的元素映射的位置是可能相同的;
一种支持删除的方法:引用计数
将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:

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

2.5 布隆过滤器优点

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

2.6布隆过滤器缺陷

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

3.海量数据面试题

3.1 哈希切割

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
解析:
利用哈希切分,将100G文件切分为1000份(如下图A0~A999),利用相同的哈希函数将文件中的IP映射到这1000个小文件中,则相同的IP肯定会被映射到同一个小文件,然后利用map<string,int>对每个小文件统计次数,同时记录出现次数最多的IP,利用pair<stirng,int>记录,小文件统计完后,出现最多的IP就得到了。
在这里插入图片描述

与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?
如上图,直接利用priority_queue<pair<string,int>,Func>建小堆,TOPK问题,需要自己写一个仿函数Func来控制比较逻辑,次数比堆顶的大,则入堆。

3.2 位图应用

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

解析:利用两个位图,两个位图对应的比特位上的数字组合为次数,例如一个元素在这两个位图上的比特位组合为

   00则代表不存在,01则代表出现110则代表出现2次,11则代表出现3次
   因为只需要找到出现一次的,所以当出现次数大于3后,则不用改变,
   这样就能统计出不存在,只出现一次,出现两次,出现大于3次的元素;

扩展:
假设题目与上面相同,但是限制只有512M的空间大小,设计算法。

解析:
如果是上面的算法,需要用两个512M的位图,一共需要1G的内存。
解法:
只需要开两个256M的位图,第一次读取100亿数据中,数据大小为( 0~231-1 )的数据, 第二次读取(231~232-1)的数据,分两次完成,这样,无论给再小的内存,都能够完成。(两次读取时用的相同的两个位图,第一次统计完后,就知道那些出现一次,然后再用该位图统计第二次,一共就用两个256M的位图)

题目二: 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
解析:
利用两个位图,将两个文件分别set进两个文件,然后利用位图的查找,如果一个元素在两个位图查找都为真,则是交集中的一个元素;

题目三: 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

解析:与题目一类似。

3.3 布隆过滤器

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

解析:

近似算法:
利用布隆过滤器,将一个文件的query set进布隆,然后去另一个文件中的query,判断在不在。

精确算法:
如图解:
在这里插入图片描述
极端情况下,可能某个文件的相同的元素太多,或则冲突的元素太多,都放到了一个文件中,导致某一个文件太大。
解决方法:
还是和上面的一样,先将小文件的元素放到set里面,因为set可以去重,如果放到set中,超出了所设内存大小,则抛异常(冲突元素过多),需要利用另一个哈希函数再进行哈希切分。

题目二: 如何扩展BloomFilter使得它支持删除元素的操作

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

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

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

相关文章

小程序实现微信 【我的】界面

小程序实现仿微信 【我的】界面 一、简介 小程序实现仿微信 【我的】界面 采用 uni-app 实现&#xff0c;可以适用微信小程序、其他各种小程序以及 APP、Web等多个平台 具体实现步骤如下&#xff1a; 下载开发者工具 HbuilderX进入 【Dcloud 插件市场】 搜索 【小程序实现…

HTML基础知识详解(上)(如何想知道html的全部基础知识点,那么只看这一篇就足够了!)

前言&#xff1a;在学习前端基础时&#xff0c;必不可少的就是三大件&#xff08;html、css、javascript &#xff09;&#xff0c;而HTML&#xff08;超文本标记语言——HyperText Markup Language&#xff09;是构成 Web 世界的一砖一瓦&#xff0c;它定义了网页内容的含义和…

SwiftUI 中无法对添加模糊(blur)效果视图截图的初步解决

概览 在 万物皆可截图:SwiftUI 中任意视图(包括List和ScrollView)截图的通用实现 这篇博文里,我们讨论了在 SwiftUI 中对任意视图截图的一般方法。 不过,经码友反应这些方法对添加模糊(blur)效果的视图好像不太灵了。这里,就让我们看看一些可能的应变(Workaround)之…

Vue3:组件间通信-各种通信方式的用法总结

Vue3组件通信和Vue2的区别&#xff1a; 移出事件总线&#xff0c;使用mitt代替。vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所有的东西&#xff0c;合并到$attrs中了。$children被砍掉了。

用顺序表实现通讯录

前言 这次的通讯录是基于上一篇的动态顺序表的基础上实现的&#xff0c;如果对动态顺序表不熟悉&#xff0c;可以打开这个链接阅读http://t.csdnimg.cn/9zJ5g&#xff0c;这里我们会调用动态顺序表的函数。 如果想看静态顺序表实现通讯录&#xff0c;可以打开这个链接阅读http:…

【Canvas与艺术】绘制蓝色波纹铜质Best Product Guaranteed徽章

【关键点】 使用贝塞尔二次曲线生成环状波纹轮廓。 【成果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>蓝色波纹铜质…

离线数仓(十)【ADS 层开发】

前言 剩下的 ADS 层主要就是写 SQL 了&#xff0c;就像我们之前练习的 HQL 题一样&#xff0c;不同的是这里的数据从哪张表读取&#xff08;DWD 还是 ADS 甚至个别表需要从 DIM 层读取&#xff09;需要我们自己来分析。 ADS 的建表语句和 MySQL 是对应的&#xff0c;我们到时候…

创意绘图小程序:绘画与实用功能的完美融合

创意绘图小程序&#xff1a;绘画与实用功能的完美融合 在数字化时代&#xff0c;创意绘图小程序以其便捷性、互动性和创新性&#xff0c;成为了人们表达自我、释放创意的新平台。本文将介绍一款集白板画、黑板画功能于一身&#xff0c;同时融合画笔调整、画布清空、橡皮擦清除…

专有钉钉微应用埋点以及本地调试埋点总结

最近在对接浙政钉&#xff0c;稳定性监控、通用采集 SDK、基础埋点、基础埋点&#xff0c;每次发布上去&#xff0c;工作人员那边反馈抓取不到信息 稳定性监控代码、通用采集 SDK index.html <!-- 流量稳定监控 S 关于埋点上线打开--><script src"https://wpk-…

在project模式下使用Implementation Runs窗口

要在“Implementation Runs”窗口中启动active implementation run&#xff0c;请执行以下任一操作&#xff1a; • 在Flow Navigator中选择“Run Implementation”。 • 在主菜单中选择“Flow > Run Implementation”。 • 从工具栏菜单中选择“Run Implementation”。 • …

达梦DMHS-Manager工具日常操作

目录 1、前言 2、同步服务管理 2.1、DMHS Agent节点管理 2.2、DMHS实例节点管理 2.3、DMHS模块节点管理 3、监控及告警 3.1、主机资源监控 3.2、同步链路监控 3.3、告警配置 4、系统管理 4.1、用户管理 4.2、角色管理 4.3、系统配置 4.4、审计信息 5、联机帮助 …

《手把手教你》系列技巧篇(七十一)-java+ selenium自动化测试-自定义类解决元素同步问题(详解教程)

1.简介 前面宏哥介绍了几种关于时间等待的方法&#xff0c;也提到了&#xff0c;在实际自动化测试脚本开发过程&#xff0c;百分之90的报错是和元素因为时间不同步而发生报错。本文介绍如何新建一个自定义的类库来解决这个元素同步问题。这样&#xff0c;我们在写脚本的时候&a…

spark高手必备

Spark 官网 https://spark.apache.org/ spark官方问题交流 Stack Overflow Newest apache-spark Questions - Stack Overflow 其它参考文档 Distributed Systems Architecture | brought to you by Alexey Grishchenko Shuffle原理 Spark Architecture: Shuffle | Distri…

C语言动态内存空间分配

1. 前言 在讲内存分配前&#xff0c;咱来聊一下为什么会有内存分配这个概念呢&#xff0c;大家都知道C语言当中是有着许多的数据类型&#xff0c;使用这些数据类型就会在内存上开辟其相对应的空间&#xff0c;那既然会开辟相应的空间&#xff0c;为什么还会有内存分配呢&#x…

函数式编程(一)

函数式编程总体介绍 函数式编程(functional programming)其实是个很古老的概念&#xff0c;诞生距今快60年啦&#xff01; 最古老的函数式编程语言Lisp 新出现的函数式编程语言&#xff1a;比如Erlang、Scala、clojure等 热门语言&#xff1a;Python、java、JavaScript、C等…

Scala第十九章节(Actor的相关概述、Actor发送和接收消息以及WordCount案例)

Scala第十九章节 章节目标 了解Actor的相关概述掌握Actor发送和接收消息掌握WordCount案例 1. Actor介绍 Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。我们学习Scala Actor的目的主要是为后续学习Akka做准备。 1.1 Java并发编程的问题 在Java并…

【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案

【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案 大家好 我是寸铁&#x1f44a; 总结了一篇【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天寸铁…

java(7)之跳转语句

1、break跳转语句 说到break其实也不是跳转&#xff0c;它更像是一个终结语句&#xff0c;常用于在循环语句需要停止出现例如 while&#xff08;&#xff09;{ if&#xff08;&#xff09;{ break&#xff1b; }} 这样的形式或者 switch&#xff08;&#xff09;{ case…

LEAP模型的能源环境发展、碳排放建模预测及不确定性分析教程

原文链接&#xff1a;LEAP模型的能源环境发展、碳排放建模预测及不确定性分析教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247599754&idx4&sn243c9f8bff355235a7056c2cbb1331fa&chksmfa82076dcdf58e7b871c3369c95ead9ff1d90baa0431318b26b6abd27…

C语言进阶课程学习记录-第23课 - #error 和 #line 使用分析

C语言进阶课程学习记录-第23课 - #error 和 #line 使用分析 实验-#errer的使用实验-缺少#error实验-#line 1的使用实验-#line 1用于标记代码小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 实验-#er…