【高阶数据结构】海量数据如何处理? (位图 布隆过滤器)

news2024/11/14 1:29:07

🌈欢迎来到高阶数据结构专栏~~位图 & 布隆过滤器


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

文章目录

  • 🌈欢迎来到高阶数据结构专栏~~位图 & 布隆过滤器
    • 一. 引入
    • 二. 位图模型
    • 三. 设计位图
      • 1️⃣set标记
      • 2️⃣reset取消标记
      • 3️⃣test检查
    • 完整代码
    • 四. 扩展问题
      • 🌏给定100亿个整数,设计算法找到只出现一次的整数
      • 🌏给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件的交集?
      • 🌏一个文件有100亿个int,1G内存,设计算法找出出现次数不超过2次的所有整数
    • 😎boss:布隆过滤器
    • 🥑控制误判
    • ⚡具体实现
      • 🎃插入
      • 🎃查找
      • 🎃删除
    • 💥优劣分析:
    • 🔥哈希切割面试题
  • 📢写在最后

请添加图片描述

一. 引入

先来道面试题:
🔥给40亿个不重复的无符号整数,没排过序;给一个无符号整数,如何快速的判断这个数是否在这40亿个数中

  • 40亿个无符号整数的空间是:大概是16G
    • 1️⃣搜索树和哈希表,都不太行。因为内存放不下:搜索树不仅仅只有数据还有三个指针和一个标记颜色,大小起码还要*4;哈希表还要2~3倍的空间
    • 2️⃣排序+ 二分查找呢?效率:log(N),但问题并不在于你搜索这个数字的效率问题,而是你在遍历也好排序也罢,这些数字在内存中放的下么?只能存在磁盘文件上,数据在磁盘上效率慢

接下来引入一个boss:位图

二. 位图模型

我们表示一个数据在还是不在,只需要一个标记值就可以,不需要真正的把这个值存储起来!使用直接定值法:(一个比特位映射标记值,1就是在,0就是不在)

在这里插入图片描述
那么此处的空间大小是多少呢?因为开的是bit位,一个字节有8个比特位,4g除8后只需512mb就可以完成,效率嘎嘎高

记住我们开的是范围,而不是数据的个数

三. 设计位图

为了方便,我们将位图用一个数组表示,让vector帮我们开辟一段连续的空间,我们只负责将数据设置或者移除就行
在这里插入图片描述

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N/8+1, 0);//永远多开一个char,防止N过小(比如10)
	}

private:
	vector<char> _bits;
};

1️⃣set标记

举例x = 20 ;
在第几个char:x / 8
在这个char里的第几个比特位:x%8

在这里插入图片描述

	void set(size_t x)
	{
		size_t i = x / 8;//在哪个char
		size_t j = x % 8;//在char中的具体位置
	
		_bits[i] |= (1 << j);
	}

2️⃣reset取消标记

为了表达j位为0,必须先取反,在比较,处理后的j位必须为0

在这里插入图片描述

	void reset(size_t x)
	{
		size_t i = x / 8;//在哪个char
		size_t j = x % 8;//在char中的具体位置

		_bits[i] &= ~(1 << j);//先取反 再与
	}

3️⃣test检查

很简单,把j位的数据与(&)上即可判断是否存在数据

	bool test(size_t x)
	{
		size_t i = x / 8;//在哪个char
		size_t j = x % 8;//在char中的具体位置

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

完整代码

namespace ljj
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bits.resize(N/8+1, 0);
		}

		void set(size_t x)
		{
			size_t i = x / 8;//在哪个char
			size_t j = x % 8;//在char中的具体位置

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


		void reset(size_t x)
		{
			size_t i = x / 8;//在哪个char
			size_t j = x % 8;//在char中的具体位置

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

		bool test(size_t x)
		{
			size_t i = x / 8;//在哪个char
			size_t j = x % 8;//在char中的具体位置

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

	private:
		vector<char> _bits;
	};
}

四. 扩展问题

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

如何设计呢?实际上是kv的统计次数搜索模型
使用两个位图来表示,满足01的就是只出现了一次的整数

0次就是 00
1次就是 01
两次及以上就是 10

在这里插入图片描述

template<size_t N>
class twobitset
{
public:
	void set(size_t x)
	{
		bool inset1 = _bs1.test(x);
		bool inset2 = _bs2.test(x);

		//00
		if (inset1 == false && inset2 == false)
		{
			//00 -> 01 变成01
			_bs2.set(x);
		}
		else if (inset1 == false && inset2 == true)
		{
			//01 -> 10
			_bs1.set(x);
			_bs2.reset(x);
		}
	}
	 
	void print_once_num()
	{
		for (size_t i = 0; i < N; i++)
		{
			if (_bs1.test(i) == false && _bs2.test(i) == true)
			{
				cout << i << endl;
			}
		}
	}

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

void test_bit_set3()
{
	int a[] = { 3, 4, 5, 2, 3, 4, 4, 4, 4, 12, 77, 65, 44, 4, 44, 99, 33, 33, 33, 6, 5, 34, 12 };

	twobitset<100> bs;
	for (auto e : a)
	{
		bs.set(e);
	}

	bs.print_once_num();
}

记过如下:

在这里插入图片描述

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

两个文件中都存在的就是交集,前提是要先去重

在这里插入图片描述

🌏一个文件有100亿个int,1G内存,设计算法找出出现次数不超过2次的所有整数

这题与上面的类似,思路大近相同:多判断一次把10->11,最后找不超过两次的整数

0次就是 00
1次就是 01
两次就是 10
三次及以上就是 11

	void set(size_t x)
	{
		bool inset1 = _bs1.test(x);
		bool inset2 = _bs2.test(x);

		//00
		if (inset1 == false && inset2 == false)
		{
			//00 -> 01 变成01
			_bs2.set(x);
		}
		else if (inset1 == false && inset2 == true)
		{
			//01 -> 10
			_bs1.set(x);
			_bs2.reset(x);
		}
		else if(inset1 == true && inset2 == false)
		{
			//10 -> 11
			_bs1.set(x);
			_bs2.set(x);
		}
	}

位图特点:

  1. 快,节省空间(直接定值法,不存在冲突)
  2. 相对局限,只能映射整形

😎boss:布隆过滤器

我们现实中也遇到过:
比如王者荣耀中要新注册一个ID的时候,你想到一个很有有趣的昵称,但此时系统告诉你 “此昵称已被注册”,这个昵称的唯一性就是运用了哈希的布隆过滤器,他本质上是就是一个 key 的模型,他只需要判断对象是否存在过就行。

此时的布隆过滤器当仁不让,布隆过滤器其实就是位图的一个变形和延申,虽然无法避免哈希冲突,但我们可以想办法降低误判的概率;当一个数据映射到位图中时,布隆过滤器会用多个哈希函数映射到多个比特位,当判断一个数据是否在位图当中时,需要分别根据这些哈希函数计算出对应的比特位,比特位设置了代表着当前状态的默认值,设置为 1 则判定为该数据存在

当然也存在误判

  1. 在:不准确的,存在误判
  2. 不在:准确的,不存在误判

在这里插入图片描述

在这里插入图片描述
🎨重点:
虽然布隆过滤器会出现误判,因为这个数据的比特位被其他数据所占有,但是判断一个数据不存在确实准确的,不存在的就是0

🥑控制误判

不可能完全去掉误判,只有尽可能的减少误判率
很显然,过小的布隆过滤器比特位很快就会都被设为 1,此时误判率就会飙升,因此布隆过滤器的长度会直接影响误判率,布隆过滤器的长度越长其误判率越小

理论而言,一个值映射的位越多,误判的概率越低,但是不敢映射太多,会造成空间消耗

大佬就此得出一个公式:
在这里插入图片描述

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

我们这里可以大概估算一下,如果使用 3 个哈希函数,那么 k 的值就为 3,ln2 的值我们取 0.7,那么 m 和 n 的关系大概是 m = 4.2 × n ,也就是过滤器长度应该是插入元素个数的 4 倍

布隆的应用:
在这里插入图片描述

⚡具体实现

因为插入过滤器的元素不仅是字符串,也可以是其他类型的数据,只有调用者能够提供对应的哈希函数将该类型的数据转换成整型即可,但一般情况下过滤器都是用来处理字符串的,我们布隆过滤器可以实现为一个模板类,所以这里可以将模板参数 T 的缺省类型设置为 string。

template<size_t N, 
class T = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
class BloomFilter
{
public:
	//...

private:
	const static size_t _ratio = 5;
	bitset<_ratio*N> _bits;
};

实例化布隆过滤器需要调用者提供三个哈希函数,由于布隆过滤器一般处理的是字符串类型的数据,因此这里我们可以默认提供几个将字符串转换成整型的哈希函数。

这里选取将字符串转换成整型的哈希函数,是综合评分最高的 BKDRHash、APHash 和 DJBHash,这三种哈希算法在多种场景下产生哈希冲突的概率是最小的

struct HashBKDR
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}

		return val;
	}
};

struct HashAP
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};

🎃插入

布隆过滤器当中需要提供一个 set 接口用于插入元素,插入元素时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后在位图中设置为1即可:

void Set(const T& key)
{
	//key传给仿函数Hash1 变成整形
	size_t i1 = Hash1()(key) % (_ratio * N);//数据不一定在范围里
	_bits.set(i1);

	size_t i2 = Hash2()(key) % (_ratio * N);
	_bits.set(i2);

	size_t i3 = Hash3()(key) % (_ratio * N);
	_bits.set(i3);
}

🎃查找

检测时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后判断这三个比特位是否被设置为1

思路:

  • 只要有一个比特位未被设置则说明该元素一定不存在(准确的)~ 反向判断,一个为0就false
  • 如果三个比特位全部被设置,则返回 true 表示该元素存在(可能仍存在误判
bool Test(const T& key)
{
	size_t i1 = Hash1()(key) % _ratio * N;

	//反向判断,一个为0就false
	if (!_bits.test(i1))
		return false;//准确的

	size_t i2 = Hash2()(key) % _ratio * N;
	if (!_bits.test(i2))
		return false;

	size_t i3 = Hash3()(key) % _ratio * N;
	if (!_bits.test(i3))
		return false;

	return true;//此处的在可能存在误判
}

🎃删除

布隆过滤器一般是不支持删除的:
因为布隆过滤器判断一个元素存在时可能存在误判,此时无法保证要删除的元素确实在过滤器当中,此时将位图中对应的比特位清 0 会影响其他元素

在这里插入图片描述

当然也不是完全没有办法的:

  • 我们只需要在每个比特位加一个计数器,当存在插入操作时,在计数器里面进行 ++
    操作,删除后对该位置进行 -- 即可

在这里插入图片描述

其实过滤器的本来目的就是为了提高效率和节省空间,在每个比特位增加额外的计数器,更是让空间开销飙升到本身的好几倍,空间消耗更多了,优势削弱了

💥优劣分析:

优势相当亮眼:

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

缺点也有

  1. 误判率,存在假阳性即不能准确判断元素是否在集合中(补救方法:再自建一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素

🔥哈希切割面试题

1️⃣给两个文件,分别有100亿个query,我们只有1G的内存如何找到两个文件的交集?(精确算法)

  • 首先我们分析大小:每个query 30个字节, 100亿个query需要3000亿个byte,也就是300G的空间(1G == 10亿字节
  • 重点:相同的query,是一定进入相同编号的小文件,再对这些文件放进内存的两个set中,编号相同的Ai和Bi小文件找交集即可
  • 小文件不是平均分的,有些进的多,有些少,如果某个小文件过大,则要递归调用另一个哈希函数

在这里插入图片描述

2️⃣给超过100G大小的log file,log中存着IP地址,设计算法找出出现次数最多的IP地址?如何找到top K 的IP?

  • 相等的ip一定会进入相同的小文件
  • 依次使用map<string,int> 对每个小文件统计次数
  • topK,建一个K个值为<ip, count>的小堆

在这里插入图片描述

📢写在最后

女拳什么时候爬?

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

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

相关文章

模拟实现list / list迭代器

前言&#xff1a;学习C的STL&#xff0c;我们不仅仅要求自己能够熟练地使用各种接口&#xff0c;我们还必须要求自己了解一下其底层的实现方法&#xff0c;这样可以帮助我们写出比较高效的代码程序&#xff01; ⭐在本篇文章中&#xff0c;list的迭代器是重点&#xff0c;它不…

WSL2配置网络代理

注意&#xff1a;本文参考自文章&#xff1a;WSL2配置代理&#xff0c;是对原文的补充&#xff0c;使其适用于河对岸云服务代理。 1 开启Windows代理 1.1 开启代理软件的局域网访问权限 请注意&#xff1a;本文的WSL2代理配置&#xff0c;需要Windows的代理软件已经能够正常…

HTTPS详解及HTTPS实验

目录 HTTPS 一&#xff0c;https在参考模型中的位置 二&#xff0c;什么是HTTPS 三&#xff0c;什么是SSL 1&#xff0c;SSL 协议分为两层&#xff1a; 2&#xff0c;SSL 协议提供的服务&#xff1a; 四&#xff0c;HTTPS的加密方式 1&#xff0c;常见的加密算法 2&#xff0c;…

mysql知识点

目录 1.mysql聚合函数&#xff1a; 2.having&#xff08;用来过滤数据&#xff09;&#xff1a; HAVING 不能单独使用&#xff0c;必须要跟 GROUP BY 一起使用 WHERE 与 HAVING 的对比 3.升序和降序 4.等于 5.实战demo&#xff1a; 1.mysql聚合函数&#xff1a; 常用的聚…

codeforces签到题之div3

前言 第一次&#xff43;&#xff4f;&#xff44;&#xff45;&#xff46;&#xff4f;&#xff52;&#xff43;&#xff45;&#xff53;&#xff0c;发现几个问题&#xff1a; 1,不知道选&#xff4c;&#xff41;&#xff4e;&#xff47;&#xff55;&#xff41;&…

17正交距阵和Gram-Schmidt正交化

标准正交向量与正交矩阵 上一节介绍过的正交向量&#xff0c;通过一个式子进行回顾&#xff0c;设q是标准正交向量组中的任意向量&#xff0c;则 这很好地表现了标准正交向量组内各向量的性质&#xff1a; 不同向量之间相互垂直&#xff08;正交&#xff09;&#xff0c;向量…

Ribbon 负载均衡

介绍Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等。简单的说&#xff0c;就…

屏幕录制软件推荐,分享这3款,简单好用

​网络上充斥着许多各种各样的屏幕录制软件&#xff0c;许多有选择困难的朋友可能会充满怀疑&#xff1a;哪个电脑屏幕录制软件很容易使用&#xff1f;屏幕录制软件推荐哪个比较好&#xff1f;别担心&#xff0c;今天&#xff0c;小编分享这这3个简单好用的屏幕录制软件&#x…

Day10 C++STL入门基础知识七——案例1【评委打分】

路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录1. 承接上文1. 案例描述2. 实现思路3. 亿点点分析3.1 创建选手类3.1.1 具体思路3.1.2 代码展示3.2 创建5名选手并对其姓名、平均分进行初始化3.2.1 具体思路① 创建vector容器② 创建一个creatPlayer()函数a.调用函数b. 初始…

若依框架基于@PreAuthorize注解的权限控制

目录 一、Java注解&#xff08;Annotation&#xff09; 1. 概述 2. Annotation通用定义 &#xff08;1&#xff09;interface &#xff08;2&#xff09;Documented &#xff08;3&#xff09;Target(ElementType.TYPE) &#xff08;4&#xff09;Retention(Ret…

IDEA插件

Lombok用注解的方式&#xff0c;简化了 JavaBean 的编写。注解下面介绍一下常用的几个注解&#xff1a;Setter 注解在类或字段&#xff0c;注解在类时为所有字段生成setter方法&#xff0c;注解在字段上时只为该字段生成setter方法。Getter 使用方法同上&#xff0c;区别在于生…

java基于ssm电梯服务管理信息系统的设计与实现源码+数据库

基于ssm电梯服务管理信息系统的设计与实现 技术支持 开发软件&#xff1a;Eclipse 项目类型&#xff1a;Webapp 数据库&#xff1a;MySQL 数据库连接池&#xff1a;druid 框架&#xff1a;SSM 数据库设计软件&#xff1a;PowerDesigner 前端界面开发&#xff1a;HTML/CSS…

Maven介绍

Maven介绍1、Maven的简单介绍2、Maven的优点3、Maven的基本知识3.1、Maven如何获取Jar包3.2、Maven仓库的分类4、Idea中的maven4.1、clean4.2、validate4.3、compile4.4、test&#xff08;不常用&#xff09;4.5、package4.6、verify&#xff08;不常用&#xff09;4.7、instal…

void*传数据,是不是像在黑洞里面拯救世界?

内核代码看到这样一个函数static inline void dev_set_drvdata(struct device *dev, void *data) {dev->driver_data data; }这个函数有什么用&#xff1f;看里面的代码含义大概就能知道&#xff0c;给 driver_data这个指针赋值&#xff0c;之后在其他地方就可以用这个指针…

《C语言高级》(二)------ 函数与指针 篇

目录 一、函数 1.1、创建和使用函数 1.2、全局变量与局部变量 1.3、函数的参数和返回 1.4、递归调用 1.5、斐波那契数列解法其三 1.6、汉诺塔 1.7、快速排序算法 二、指针 2.1、初识指针 2.2、指针与数组 2.3、多级指针 2.4、指针数组与数组指针 2.5、指针函数与函数指…

SpringCloud入门实战(五)-集成Ribbon

一、Ribbon简介 Spring Cloud Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等。简单地说&#xff0c;就是在配置文件中列出Load Balancer(简称LB)后面所有…

python爬虫学习笔记-mongodb安装基本介绍pymongo使用

MongoDB数据存储 MongoDB是一个非关系型数据库(NoSQL). 非常适合超大数据集的存储, 由 C 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系…

Spring Cloud_OpenFeign服务接口调用

目录一、概述1.OpenFeign是什么2.能干嘛二、OpenFeign使用步骤1.接口注解2.新建Module3.POM4.YML5.主启动类6.业务类7.测试8.小总结三、OpenFeign超时控制1.超时设置&#xff0c;故意设置超时演示出错情况2.是什么3.YML中需要开启OpenFeign客户端超时控制四、OpenFeign日志打印…

论文投稿指南——中文核心期刊推荐(水路运输)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

Json的语法及使用

Json的语法及使用前言一、Json是什么&#xff1f;二、Json语法三、Json示例前言 在数据传输时用到Json格式&#xff0c;在此稍作记录。 一、Json是什么&#xff1f; JSON 指的是 JavaScript 对象表示法&#xff08;JavaScript Object Notation&#xff09;JSON 是轻量级的文本…