【C++进阶08】哈希的应用(位图and布隆过滤器)

news2024/12/28 4:53:11

在这里插入图片描述

一、位图

1.1 位图的概念

面试题

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

能想到的解决思路:

  1. 遍历,时间复杂度O(N)
  2. 排序(O(NlogN)) + 利用二分查找: logN
  3. 放到哈希表或红黑树

40亿整数就是16GB,无法全部加载到内存
遍历、排序和二分查找就都不太现实
虽然可以在文件中归并,但就慢了很多
文件中不能用下标,自然无法二分查找

虽然可以将数据一段一段放进哈希表和红黑树
但每次将数据插入进红黑树又释放
相当于暴力查找40亿数据
红黑树的特性完全没用上

所以以上3点都是不合适的
最大的原因就是内存不足

位图解决
数据是否在给定的整形数据中
结果是在或者不在,刚好是两种状态
那么可以用比特位表示数据是否存在
1为存在,0为不存在

比如数据{1,2,4,9,1517,23}在位图的样子
在这里插入图片描述
所谓位图,就是用每一位来存放某种状态
适用于海量数据,数据无重复的场景
通常是用来判断某个数据存不存在的

一个比特位就能表示一个整型数据在或不在
一个整型就是32比特位,相当于缩小了32倍
也就是说16G的数据只需要0.5G

1.2 位图的模拟实现

在这里插入图片描述
位图的三个主要接口:

  1. set:将数据映射位置置成1,表示存在
  2. reset:将数据映射位置置成0,表示删除
  3. test:检测数据是否存在于位图
template <size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N / 8 + 1, 0); // 需求N个比特位,按字节给,所以除8.除会去余,所以加1
	}

	void set(size_t x) // 将x比特位置1
	{
		size_t i = x / 8; // 计算x映射的位在第i个char数组位置
		size_t j = x % 8; // 计算x映射的位在这个char的第j个比特位
		
		_bits[i] |= (1 << j);
	}

	void reset(size_t x) // 将x比特位置0
	{
		size_t i = x / 8;
		size_t j = x % 8;

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

	bool test(size_t x) // 检测位图中x是否为1
	{
		size_t i = x / 8; 
		size_t j = x % 8; 
		
		return _bits[i] & (1 << j);
	}
private:
	vector<char> _bits;
};

1.3 位图的应用

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

方法:

  1. 创建两个位图对象bs1、bs2
  2. 遍历数据
    出现0次用00表示
    出现1次用01表示
    出现2次用10表示
    出现3次及以上用11表示

如果数据映射的位置在bs1里为1
在bs2里为0表示此数据出现过2次
如果在bs1和bs2里都为1,表示出现3次及以上
方法实现:

template <size_t N>
class twobitset
{
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);
		}
		// 10 不变
		
	}

	void print()
	{
		for (size_t i = 0; i < N; i++) 
		{
			if (_bs2.test(i))
				cout << i << " ";
		}
	}
private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

void test_twobitset3()
{
	twobitset<1000> bs;
	int a[] = { 0, 12, 12, 0, 20, 12, 12, 0, 223, 22, 45, 4 };
	for (auto e : a)
	{
		bs.set(e);
	}

	bs.print();
}

2、给两个文件,分别有100亿个整数
我们只有1G内存,如何找到两个文件交集?

方法1:
其中的一个文件读到内存的位图中
再读另一个文件,判断在不在上面的位图
在就是交集

问题:
找出的交集存在重复值

解决方法1:
一个数为交集就在第一个文件set那个数

解决方法2:
读取文件1映射到位图1
读取文件2映射到位图2
判断数据映射的位置在这两个位图中
是否都为1

for (int i = 0; i < N; i++)
{
	if (bs1.test(i) && bs2.test(i))
	{
		// 交集
	}
}

或者按位与这两个位图

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

定义两个位图对象
将数据插入到位图
出现0次用00表示
出现1次用01表示
出现2次用10表示
出现3次及以上用11表示

除了两个位图对应位置都为1
其他都打印

二、布隆过滤器

位图的优点:

  1. 速度快、节省空间

缺点:

  1. 只能映射整形,其他如浮点数、string等
    不能存储映射

布隆过滤器便是解决此缺点

2.1 布隆过滤器提出

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

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

2.2 布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)
在1970年提出的 一种紧凑型的、比较巧妙的概
率型数据结构,特点:高效地插入和查询
可以用来告诉你
“某样东西一定不存在或者可能存在”
它是用多个哈希函数
将一个数据映射到位图结构中
此种方式不仅可以提升查询效率
也可以节省大量的内存空间
在这里插入图片描述
用多个哈希函数将字符映射到不同的位置
以此降低重复率,查找时在所有映射的位置
查看是否均为1

查找一个值在与不在
在:是不准确的,存在误判
不在:是准确的
比如美团,本来不在
查找时每个映射的位置都跟别人冲突
导致认为它在

2.3 布隆过滤器的使用场景

  1. 能容忍误判场景
    比如:改名时,快速判断昵称是否使用过

昵称在数据库,而数据库在磁盘
如果去磁盘查找修改的昵称是否使用过
效率非常慢,我们平时改昵称时
只要输入就能立即反馈昵称是否使用

这时可以把所有用户昵称放入布隆过滤器
即使误判修改昵称已使用,用户也感知不到
(只要误判率不高还是可以接受的)

2.4 布隆过滤器的实现

如何选择哈希函数个数和布隆过滤器长度
k 为哈希函数个数
m 为布隆过滤器长度
n 为插入的元素个数
p 为误报率
在这里插入图片描述
在这里插入图片描述
代码实现:

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

struct APHash
{
	size_t operator()(const string& s) // 仿函数的作用:把一个类当作对象去访问或把一个对象像函数去使用
	{
		size_t hash = 0; // 加register放到最前面表示建议变量放到寄存器里面
		for (long i = 0; i < s.size(); i++)
		{
			size_t ch = s[i];
			if ((i & 1) == 0) // i 是偶数走if,i 是奇数else.奇数二进制的低位一定是1,按位与1得到的便是奇数
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			else
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

// N是key,插入值的个数
template<size_t N, 
class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = N * _X;
		size_t hash1 = Hash1()(key) % len; // 用Hash仿函数转成可以取模的整型值,模N是怕转出来的值超出N
		_bs.set(hash1);

		size_t hash2 = Hash2()(key) % len;
		_bs.set(hash2);

		size_t hash3 = Hash3()(key) % len;
		_bs.set(hash3);
		cout << hash1 << " " << hash2 << " " << hash3 << endl;
	}

	bool test(const K& key) // 判断3个位置,有一个位置为0就是不在
	{
		size_t len = N * _X;
		size_t hash1 = Hash1()(key) % len; 
		if (!_bs.test(hash1))
			return false;
		size_t hash2 = Hash2()(key) % len;
		if (!_bs.test(hash2))
			return false;

		size_t hash3 = Hash3()(key) % len;
		if (!_bs.test(hash3))
			return false;
		return true;
	}
private:
	static const size_t _X = 6; // 比重为6,冲突率5%以内.比重建议5-10(冲突率1%-10%)
	bitset<_X * N> _bs; // 开_X倍的空间,空间开得越大,冲突率越低
};

2.5 布隆过滤器的应用

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

100亿query:假设每个query平均50byte
100亿就是5000亿byte,也就是大约占用500G

  1. 可以把每个文件分成1000份读进内存
    每份是0.5个G

在这里插入图片描述
B文件的每一份在A文件的每一份里去找
但是这样时间复杂度太高了
于是可以用哈希函数来切分文件
哈希切分:i = HashFunc(query) % 1000
每个query,算出i是多少就进入Ai小文件
B也是一样,算出i放进Bi小文件
B0、B1……在对应的A0、A1……
小文件里去找,找到了就是交集

跟之前的哈希桶很像
进入同一个桶的都是冲突的值
在这里A和B相同的值用的同一个哈希函数
便一定会进入同一个编号的文件
在这里插入图片描述
会导致的问题:
因为不是平均切分,可能会出现冲突多
每个Ai、Bi小文件过大

  1. 单个文件大量重复query
  2. 单个文件大量不同query

解决方法:
直接使用unordered_set/set
依次读取文件query,插入set中

  1. 如果读取整个小文件query
    都可以成功插入set,说明是情况1
  2. 如果读取整个小文件query
    插入过程抛异常则是情况2
    换其他哈希函数,再次分割求交集

说明:set插入key如果已经有了返回false
如果内存不足抛bad_alloc异常
剩下的都会成功

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

删除一个元素可能会影响其它元素
用多个位图计数的方式表示每个位置被映射了几次
删除时减减该位置

3、哈希切割
给一个超过100G大小的log file, log中存着IP地址,
设计算法找到出现次数最多的IP地址?
与上题条件相同,如何找到top K的IP?

解决方法:
哈希切分成500个小文件
依次读取数据,i = HashFunc(ip)%500
这个ip就是第i个小文件

依次处理每个小文件
使用unordered_map/map统计ip出现次数

  1. 统计过程抛异常,说明单个文件过大
    冲突太多,需要重新换哈希函数
    再次哈希切分这个小文件
  2. 如果没有抛异常,则正常统计
    统计完一个小文件,记录最大的
    clear map,再统计下一个小文件

总结特点:
相同的ip一定进入相同的小文件
读取单个小文件就可以统计ip出现次数

在这里插入图片描述
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见

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

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

相关文章

计算机毕业设计 | SpringBoot+vue 教务管理系统(附源码)

1&#xff0c;项目背景 教育需求增长 随着社会的发展&#xff0c;对于教育质量的要求也在不断提高。传统的手工操作和纸质记录已经不能满足现代教学的需求。因此&#xff0c;一个自动化、数字化的教务管理系统成为了必然的选择。 信息化趋势 现代科技的飞速发展使得信息化成为…

【算法】登山(线性DP,最长上升)

题目 五一到了&#xff0c;ACM队组织大家去登山观光&#xff0c;队员们发现山上一共有N个景点&#xff0c;并且决定按照顺序来浏览这些景点&#xff0c;即每次所浏览景点的编号都要大于前一个浏览景点的编号。 同时队员们还有另一个登山习惯&#xff0c;就是不连续浏览海拔相…

如何保证MySQL数据一致性

在当今大数据时代&#xff0c;数据库系统扮演着至关重要的角色&#xff0c;而MySQL作为一种流行的关系型数据库管理系统&#xff0c;在数据一致性方面拥有着丰富的机制和技术。下面简单的探讨MySQL是如何保证数据一致性的。 事务与ACID特性 要了解MySQL如何保证数据一致性&am…

excel中去掉单元格中两个数字之间的空格

excel中去掉单元格中两个数字之间的空格 使用公式&#xff1a;SUBSTITUTE(A1," “,”") 解释&#xff1a;将A1单元格中的空格查找出来并去掉。

香蕉派BPI-M7 瑞芯微RK3588 人工智能开源硬件开发板公开发售

香蕉派(Banana Pi) BPI-M7瑞芯微K3588开源硬件单板计算机公开销售&#xff0c;支持WiFi 6和BT5.2&#xff0c;硬件有3个版本:8G Ram64G eMMC, 16G Ram128 eMMC和32G Ram128 eMMC 香蕉派BPI-M7采用睿芯最新旗舰RK3588八核64位处理器&#xff0c;最高频率为2.4GHz, 6 TOPS NPU&…

网络安全全栈培训笔记(59-服务攻防-中间件安全CVE复现lSApacheTomcataNginx)

第59天 服务攻防-中间件安全&CVE复现&lS&Apache&Tomcata&Nginx 知识点&#xff1a; 中间件及框架列表&#xff1a; lIS,Apache,Nginx,Tomcat,Docker,Weblogic,JBoos,WebSphere,Jenkins, GlassFish,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Sprng,Flask,…

MicrosoftEdge浏览器打开网页出现“此网站被人举报不安全”问题时解决办法

1&#xff1a;有时候不知怎么回事用电脑自带的微软浏览器进行搜索会出现以下的问题 这可能是由于我们的浏览器安全审查过于严格引起的 Windows10正式版系统下&#xff0c;使用Edge浏览器浏览网页时候&#xff0c;发现整个页面突然变成了红色&#xff0c;显示“已有人举报此网站…

【项目管理】立项管理

一、前言 对于甲方的立项&#xff1a;需求调研二编写项目申请书一可行性研究&#xff08;机会、初步、详细&#xff09;一项目论证一项目评估一评审获得批准一发布招标文件&#xff01;对于乙方的立项&#xff1a;看到招标文件一进行项目识别一可行性研究&#xff08;机会、初…

MATLAB - 控制小车上的倒立摆

系列文章目录 前言 一、小车 - 摆杆 小车 - 摆杆模型如图 1 所示&#xff0c;使用 Simscape™ Multibody™ 在 Simulink 中建模。 图 1&#xff1a;小车上的倒立摆 图 2&#xff1a;Simscape 多体模型 该系统通过对小车施加可变力 进行控制。控制器需要在将小车移动到新位置或…

【OpenCV】在Linux上使用OpenCvSharp

前言 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;它具有C&#xff0c;Python&#xff0c;Java和MATLAB接口&#xff0c;并支持Windows&#xff0c;Linux&#xff0c;Android和Mac OS。OpenCvSharp是一个Ope…

Python中类的相关术语(附带案例)

目录 1、面向对象 2、类 3、实例 4、初始化方法 5、魔法方法 6、字符串方法 7、self 8、数据、属性、操作、行为 9、父类、基类、超类 or 子类、派生类 10、多态 11、重载多态 and 重写多态 12、名称解释 1、面向对象 在Python中&#xff0c;面向对象编程&…

由《幻兽帕鲁》私服漏洞引发的攻击面思考

《幻兽帕鲁》私服意外丢档 当了一天的帕鲁&#xff0c;回家开机抓帕鲁的时候发现服务器无法连接。运维工具看了下系统负载发现 CPU 已经跑满。 故障排查 登录服务器进行排查发现存在可疑的 docker 进程。 经过一番艰苦的溯源&#xff0c;终于在命令行历史中发现了端倪 攻击…

深入浅出AI落地应用分析:AI个人助手Monica

前言:铺天盖地的大模型以及所谓的应用到目前为止实际还是很少有像Monica这样贴合个人工作习惯的产品落地,比如像Chatgpt等这样的产品,绝大多数人不会专门买🪜翻墙出去用,而且大多数场景下素人或小白都不知道该怎么用,但是Monica这款产品就很好的以浏览器的插件的形式始终…

[BUUCTF]-PWN:cmcc_pwnme2解析

保护 ida 完整exp&#xff1a; from pwn import* context(log_leveldebug) #premote(node5.buuoj.cn,26964) pprocess(./pwnme2) addhome0x8048644 addflag0x8048682 getfile0x80485CB main0x80486F8 pop_ebp0x8048680 ret0x80483f2 pop_ebx0x8048409 pop_edi_ebp0x804867f st…

贪吃蛇---C语言---详解

引言 C语言已经学了不短的时间的&#xff0c;这期间已经开始C和Python的学习&#xff0c;想给我的C语言收个尾&#xff0c;想起了小时候见过别人的老人机上的贪吃蛇游戏&#xff0c;自己父母的手机又没有这个游戏&#xff0c;当时成为了我的一大遗憾&#xff0c;这两天发现C语…

Unity | 资源热更(YooAsset AB)

目录 一、AssetBundle 1. 插件AssetBundle Browser 打AB包 &#xff08;1&#xff09;Unity&#xff08;我用的版本是2020.3.8&#xff09;导入AssetBundle Browser &#xff08;2&#xff09;设置Prefab &#xff08;3&#xff09;AssetBundleBrowser面板 2. 代码打AB包…

算法:积木游戏学习数学

一、算法描述 小华和小微一起通过玩积木游戏学习数学。 他们有很多积木&#xff0c;每个积木块上都有一个数字&#xff0c;积木块上的数字可能相同。 小华随机拿一些积木挨着排成一排&#xff0c;请小微找到这排积木中数字相同且所处位置最远的2块积木块&#xff0c;计算他们的…

【LeetCode】每日一题 2024_1_30 使循环数组所有元素相等的最少秒数(哈希、贪心、扩散)

文章目录 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01;题目&#xff1a;使循环数组所有元素相等的最少秒数题目描述代码与解题思路 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 今天的题目类型差不多是第一次见到&#xff0c;原来题目描述…

【大数据】Flink 架构(四):状态管理

《Flink 架构》系列&#xff08;已完结&#xff09;&#xff0c;共包含以下 6 篇文章&#xff1a; Flink 架构&#xff08;一&#xff09;&#xff1a;系统架构Flink 架构&#xff08;二&#xff09;&#xff1a;数据传输Flink 架构&#xff08;三&#xff09;&#xff1a;事件…

GPIO中断

1.EXTI简介 EXTI是External Interrupt的缩写&#xff0c;指外部中断。在嵌入式系统中&#xff0c;外部中断是一种用于处理外部事件的机制。当外部事件发生时&#xff08;比如按下按钮、传感器信号变化等&#xff09;&#xff0c;外部中断可以立即打断正在执行的程序&#xff0…