【C++】位图+哈希切割+布隆过滤器

news2024/10/7 2:29:56

文章目录

  • 一、位图
    • 1.1 位图概念
    • 1.2 位图实现
      • 1.2.1 把x对应比特位0置1
      • 1.2.2 把x对应比特位1置0
      • 1.2.1 查看x对应比特位
    • 1.3 位图源码
    • 1.4 位图的应用
  • 二、哈希切割(处理海量数据)
  • 三、布隆过滤器
    • 3.1 布隆过滤器的概念
    • 3.2 布隆过滤器的应用场景
    • 3.3 布隆过滤器的实现
      • 3.3.1 布隆过滤器长度的设置
      • 3.3.2 插入操作
      • 3.3.3 查找操作
      • 3.3.4 误判测试
      • 3.3.5 布隆过滤器删除
    • 3.4 布隆过滤器的应用

一、位图

先看一道例题:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

首先要知道,1G约等于10亿字节
那么40亿个整形就是160亿个字节,约等于16G
【遍历或者排序+二分】 他们都需要存入数组中,但是内存没有空间能够创建16G大小的数组。(❌)
【红黑树和哈希】 红黑树不仅要存放数字,还得存放指针。(❌)
而哈希表也要存放指针和负载因子。(❌)

1.1 位图概念

【位图】 数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,我只需要判断在还是不在即可。那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。(✔)

我们知道无符号整数的范围是0 ~ 2^32 - 1,所以我们开一个2^32大小的数组。也就是2^32个比特位,因为一个整形是32个比特位,所以用位图开出的空间大小为:16G/32 = 0.5G = 512M

接下来我们就可以使用直接定址法是几就在第几个位置把该比特位置为1

而我们开int类型和char类型都无所谓,如果是char,就是8个比特位,第一个元素就可以表示0 ~ 7,第二个元素则表示8 ~ 15。如果是int类型就是32个比特位,第一个元素就可以表示0 ~ 31,第二个元素则表示32 ~ 63

当我们要查找一个值x的时候,我们需要知道它在第几个元素的第几个比特位上,怎么办呢?
【char】在第x/8个元素上。在该元素的第x%8个比特位上。
【int】在第x/32个元素上。在该元素的第x%32个比特位上。

1.2 位图实现

这里我们使用vector<char>类型的位图。
那么我们首先就要初始化好:把每个比特位都置为0,那么vector开多大呢?

我们可以使用非类型模板参数,template <size_t N>,那么我们就要开N / 8 + 1大小的空间。

template <size_t N>
class BitSet
{
public:
	BitSet()
	{
		_bits.resize(N / 8 + 1, 0);
	}
private:
	vector<char> _bits;
};

1.2.1 把x对应比特位0置1

我们按照上面说的/8%8获得具体位置,加下来我们需要把这个位置置为1,其他位置不变,我们可以把1左移然后|=运算。
有人可能会如果是int类型,就跟大小端有关系,其实不管是大端还是小端。

左移是向高位移动
右移是向低位移动

void set(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] |= (1 << j);
}

1.2.2 把x对应比特位1置0

我们只能把x对应的比特位变成0,其他位置不能变,那么我们可以先用上面的方法找到位置,然后将1左移然后先取反再&=运算

void reset(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] &= (~(1 << j));
}

1.2.1 查看x对应比特位

还是按照上面的方法找到具体位置后,把1左移到该位置,返回两个&的结果。

bool search(int x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	return _bits[i] & (1 << j);
}

1.3 位图源码

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

		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] |= (1 << j);
		}

		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] &= (~(1 << j));
		}

		bool search(int x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _bits[i] & (1 << j);
		}
	private:
		std::vector<char> _bits;
	};
}

在这里插入图片描述
上面就是为了开辟42亿个比特的大小,因为-1的无符号数字就是2^32 - 1,当然也可以写成0xffffffff
验证一下所占内存大小:
在这里插入图片描述

1.4 位图的应用

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

这里的关键是注意到只出现一次,这样我们就可以列出三种状态:
1️⃣ 出现0次
2️⃣ 出现1次
3️⃣ 出现1次以上
我们只需要两个比特位就可以表示出三个状态。
上面的位图是用一个位图中的一个比特位标定一个数字出没出现,那么这里我们可以用两个位图的两个比特位标定一个数字出现次数。
假如现在是看0这个数字:
在这里插入图片描述

template <size_t N>
class TwoBitSet
{
public:
	void set(size_t x)
	{
		if (!_b1.search(x) && !_b2.search(x))// 00
		{
			_b2.set(x);// 01
		}
		else if (!_b1.search(x) && _b2.search(x))// 01
		{
			_b1.set(x);
			_b2.reset(x);// 10
		}
		// 10不变
	}

	void PrintOnce()
	{
		for (size_t i = 0; i < N; i++)
		{
			if (!_b1.search(i) && _b2.search(i))
				std::cout << i << std::endl;
		}
	}

private:
	BitSet<N> _b1;
	BitSet<N> _b2;
};

在这里插入图片描述
【第二题】
给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

思路一: 先把一个位图中的数据放入位图中,然后遍历另一个文件寻找,找出交集。
但是有可能会出现重复元素,要注意去重。
思路二: 两个文件的元素放进两个位图中,放进去的过程就各自去重了,然后两个&运算即可判断是否有交集。

【第三题】
1 个文件有 100 亿个 int,1G内存,设计算法找到出现次数不超过2次的所有整数

这个题跟第一题类似,可以分为四种状态:
1️⃣ 出现0次
2️⃣ 出现1次
3️⃣ 出现2次
4️⃣ 出现3次以上
所以一样可以用两个位图表示四种状态。

【第四题】
给一个超过100G大小的log file,log中存着IP地址,设计算法找到出现次数最多的IP地址

这里就不能用位图了,因为位图的作用是统计在不在,统计次数还得使用map
我们可以把文件切分成多个小文件,这里的切分也是有讲究的,如果平均切分,在每个小文件统计的话结果不正确,因为可能一个IP有多份分在多个文件。而我们map统计完一个小文件就要清空再统计下一个文件,不然内存不够用。
所以这里我们需要使用哈希切割

二、哈希切割(处理海量数据)

前面我们学过为了实现哈希映射,我们需要一个哈希函数,这里我们也可以使用哈希函数把IP转为整型。比方说我们分成了100份小文件,idx = HashFunc(IP) % 100,idx是几就把它放进几号文件中。

我们可以把每个小文件理解为一个哈希桶
这样不一样的IP可能分进同一个小文件中,但是同一个IP一定会分进同一个小文件。

这里还可能出现一个情况:其中一个小文件的大小可能超过1G(假设超过1G就不够了)。
而超过了1G也有有两种情况:

1️⃣ 不重复的IP很多,map需要很多节点,统计不下。
2️⃣ 重复的IP很多,map不需要很多节点,统计的下。

针对第一种情况,我们可以换个哈希函数递归切分。
但是这种方法对情况二无效,因为相同的IP太多,照样会切分超过1G。

所以综合考虑可以这样统计:

不管是啥情况,都直接用map统计,如果是第二种情况就直接统计完成了。如果是第一种情况,会insert失败,我们可以捕获异常,此时再去换个哈希函数递归切分。

三、布隆过滤器

通过上面的讲解我们可以看出
位图优点是节省空间和效率高
缺点是要求范围相对集中,而且只能是整型。

而如果是字符串我们想使用位图,就可以使用哈希函数转成整型
这里就会有一种情况,不同的字符串可能转换成同一个整型。 会导致误判。

在这里插入图片描述

存在是不准确的,如果只有str1和str2,而str3映射的位置跟str2重了,就会导致原本不在的元素误判成在。

那我们如何降低误判率呢?答案是使用布隆过滤器

3.1 布隆过滤器的概念

它的主要思想是让一个值映射多个位置。我们可以使用多个哈希函数,多映射几个位置,这里假设有两个哈希函数,映射两个位置。

在这里插入图片描述

这样我们要看str2是否存在,必须要同时指向红色和绿色才能判断为存在。

所以布隆过滤器的作用就是降低误判率。映射的位置越多,误判率越低。
但是这里映射的位置也不能太多,映射的多,占的空间也多,找的次数也多,我们使用位图这样的方式就是为了提高效率并且节省空间。映射的多了也就没那么节省空间了。

3.2 布隆过滤器的应用场景

【场景一】
当我们要写一个注册系统的时候,我们注册昵称的时候不能跟别人重复,此时我们就可以采用布隆过滤器,如果不在那么就是准确的,一定不存在。但是如果显示存在,则有可能是误判。因为布隆过滤器中如果存在可能会误判,可以到数据库中再次查询昵称号码存不存在。

在这里插入图片描述
有人可能问这有必要加一个布隆过滤器吗?

假设现在来了100不存在的值,大部分都会显示不存在,只有很小一部分会误判为存在,这样没有误判的大部分效率大大提高。

【场景二】
我们在访问网站的时候有时候会出现风险网站。我们可以把这些网页加入黑名单,在我们访问网站之前就先经过布隆过滤器,有风险就可以快速的判断。

3.3 布隆过滤器的实现

布隆过滤器最常见的是string类型。 这里要给一个非类型模板参数N以确定开的空间有多大,这里我们写三个哈希函数。而字符串转整型的哈希函数有很多:
各种字符串Hash函数

这里我们就取里面效率较高的三个:

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

struct APHash
{
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (long 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 DJBHash
{
	size_t operator()(const std::string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

3.3.1 布隆过滤器长度的设置

关于长度的问题这里有专门的文章进行讲述:
详解布隆过滤器的原理
里面有一个公式:
在这里插入图片描述
这里n我们是知道的,假设k是3,ln2约等于0.7,最后得到m=4.2*n,所以布隆过滤器多一个数据要开大约4.2个比特位,我们直接按加入一个数据开5个比特位算

template<size_t N,
class K = std::string,
class HashFunc1 = BKDRHash,
class HashFunc2 = APHash,
class HashFunc3 = DJBHash>
class BloomFilter
{
public:
private:
	std::bitset<N * 5> _bs;
};

3.3.2 插入操作

大致思路跟我们上面的位图一样,这里我们使用库里的函数bitset头文件:#include <bitset>而set函数库里面已经帮我们实现好了:

void set(const K& x)
{
	size_t idx1 = HashFunc1()(x) % (5 * N);
	size_t idx2 = HashFunc2()(x) % (5 * N);
	size_t idx3 = HashFunc3()(x) % (5 * N);
	_bs.set(idx1);
	_bs.set(idx2);
	_bs.set(idx3);
}

3.3.3 查找操作

这里只要有一处不在那么就返回false,全部都在才能返回true。

bool test(const K& x)
{
	size_t idx1 = HashFunc1()(x) % (5 * N);
	if (!_bs.test(idx1))
	{
		return false;
	}
	size_t idx2 = HashFunc2()(x) % (5 * N);
	if (!_bs.test(idx2))
	{
		return false;
	}
	size_t idx3 = HashFunc3()(x) % (5 * N);
	if (!_bs.test(idx3))
	{
		return false;
	}
	return true;
}

3.3.4 误判测试

std::string arr[] = { "北京", "武汉", "广州", "上海", "北京", "北京", "广州",
	"上海", "上海" };
		BloomFilter<10> bs;
		for (auto& e : arr)
		{
			bs.set(e);
		}
		for (auto& e : arr)
		{
			std::cout << bs.test(e) << std::endl;
		}
		std::cout << std::endl;

在这里插入图片描述
结果没有问题,接下来我们测试误判,加上:

// 测试误判
srand(time(0));
for (auto& e : arr)
{
	std::cout << bs.test(e + std::to_string(rand())) << std::endl;
}

在这里插入图片描述
可以看到出现了误判。

3.3.5 布隆过滤器删除

布隆过滤器一般不能支持删除,因为一个位置可能被多个值映射,删除以后可能把别人的也删掉了。

那么我们能不能强制支持删除呢?

我们可以去计数,有几个值映射计数器就是几,删除了就让当前位置的计数器--
但是使用计数又会有问题:因为不知道计数器的范围,所以不能开的太小的比特位,导致使用过多内存

3.4 布隆过滤器的应用

【第一题】
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和 近似算法。(query就是sql语句,可以理解为一个字符串。,也可能是网络请求url,也就是网址)

近似算法我们直接使用布隆过滤器,将一个文件的query语句放进布隆过滤器里,然后另一个文件查找在不在就是交集。虽然有误判:不存在的也被当做交集。但是作为近似算法还是可行的。
精确算法就得用到前面的哈希切割。同时把两个文件都切分成数个小文件,在编号相同的小文件查看交集即可,最后注意去重。



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

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

相关文章

zookeeper安装使用

一、因使用kafka 需使用zookeeper,此处使用单节点 ZooKeeper有两种安装模式&#xff0c;最简单的方式是单机模式&#xff08;standalone mode&#xff09;&#xff0c;它只需要在一台机器上面运行&#xff0c;另一种方式是集群模式&#xff0c;集群模式需要多台服务器部署。 Z…

Java中垃圾回收(GC)基本概念

如果想真正理解GC&#xff0c;则需要循序渐进&#xff0c;由浅入深的了解GC&#xff0c;从本篇文章开始我们详细介绍Java中的GC&#xff0c;本篇文章我们通过4个主题先介绍垃圾回收的基本概念一、Java中什么是GC&#xff0c;为什么需要GC二、早期垃圾回收三、Java垃圾回收机制四…

DockQuery x 达梦 国产数据库生态“加速跑”

「数字化」是当今社会最先进和最具穿透力的生产力&#xff0c;近十年里开展着气势磅礴的发展。而信创产业则是保障中国经济数字化转型平稳健康发展的基础。 随着信创产业规模不断扩大&#xff0c;国产数据库市场释放出前所未有的活力。达梦作为国产数据库领头羊&#xff0c;坚…

Redis进阶之事物持久化

Reis进阶Redis事物Redis管道Redis持久化RDB持久化RDB持久化优缺点分析RBD文件修复&禁用RDB快照AOF持久化AOF优缺点&AOF重写机制AOF&RDB混合写机制Redis事物 什么是事物&#xff1f;相信学过数据库的铁子们都知道事物是什么。在MySQL当中事物是指和数据库连接的一次…

在面试时候,如何简明扼要简述产品流程

下面这个图是我今天总结的&#xff0c;我把它上传到这里来&#xff0c;然后逐一按点来解释&#xff0c;为了迎合面试&#xff0c;所以每个点尽量不超过140字。(觉得OK的&#xff0c;请点赞哦&#xff01;)图片过大&#xff0c;请点击放大后按“F”键查看原图。或下载后查看&…

IT项目经理的自我修养手册

在不断进步的时代&#xff0c;任何岗位职责都是一个责任、权力与义务的综合体&#xff0c;有多大的权力就应该承担多大的责任&#xff0c;有多大的权力和责任应该尽多大的义务&#xff0c;任何割裂开来的做法都会发生问题。那么作为IT项目经理的岗位职责&#xff0c;我大概列举…

vue实现输入框中输完后光标自动跳到下一个输入框中

前言 最近接到这么一个需求&#xff0c;做一个安全码的输入框&#xff0c;限制为6位数&#xff0c;但是每一个写入的值都是一个输入框&#xff0c;共计6个输入框&#xff0c;当前输入框写入值后&#xff0c;光标自动跳到下一个输入框中&#xff0c;删除当前输入框写入的值后再自…

秒懂算法 | 基于主成分分析法、随机森林算法和SVM算法的人脸识别问题

本文的任务与手写数字识别非常相似,都是基于图片的多分类任务,也都是有监督的。 01、数据集介绍与分析 ORL人脸数据集共包含40个不同人的400张图像,是在1992年4月至1994年4月期间由英国剑桥的Olivetti研究实验室创建。 此数据集下包含40个目录,每个目录下有10张图像,每个…

Mysql下载安装详细笔记

MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关系数据库管理系统) 应用软件之一。 一、下载mysql安装包 1. 登录官方网站https://www.mysql.com/ 2. 进入Down…

[Latex]参考文献的格式:数字,作者+年份

参考资料: 《使用 natbib 进行参考文献管理》 《bibliographystyle类型》 《\usepackage{natbib}在latex模板写作》 《LaTeX中的参考文献——作者年代引用》 文章目录[TOC]一、共同的参考文献和正文二、参考文献的引入方法2.1 声明引入的使用包(usepackage)2.2 正文的引用\…

C语言-基础了解-09-C循环

C循环 一、C循环 循环语句允许我们多次执行一个语句或语句组&#xff0c;下面是大多数编程语言中循环语句的流程图&#xff1a; 二、循环类型 2.1 while 循环 当给定条件为真时&#xff0c;重复语句或语句组。它会在执行循环主体之前测试条件。 语法 while(condition) { …

【虹科案例】虹科任意波形发生器在量子计算中的应用

虹科AWG在量子计算中的应用精度在研究中始终很重要&#xff0c;很少有研究领域需要比量子研究更高的精度。奥地利因斯布鲁克大学的量子光学和量子信息研究所需要一个任意波形发生器&#xff08;AWG&#xff09;来为他们的研究生成各种各样的信号。01无线电频率第一个应用是在射…

C++——类型转换

目录 C语言中的类型转换 C强制类型转换 static_cast reinterpret_cast const_cast dynamic_cast 延伸问题 RTTI&#xff08;了解&#xff09; C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或…

JAVA开发(Spring Gateway 的原理和使用)

在springCloud的架构中&#xff0c;业务服务都是以微服务来划分的&#xff0c;每个服务可能都有自己的地址和端口。如果前端或者说是客户端直接去调用不同的微服务的话&#xff0c;就要配置不同的地址。其实这是一个解耦和去中心化出现的弊端。所以springCloud体系中&#xff0…

aws apigateway 使用restapi集成http

参考资料 https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/services-apigateway-tutorial.html restapi代理集成http 在 HTTP 代理集成中&#xff0c;apigateway会将客户端提交的方法请求传递至后端。传递的请求数据包括请求标头、查询字符串参数、URL 路径变量和paylo…

SVN项目迁移到Git方法

本文记录如何将SVN项目迁移到Git&#xff0c;并保留提交日志信息。 目录Git和SVN差异环境准备Git安装、配置项目迁移1. 将源SVN库转换到Git本地仓库2. 添加Git远程库地址3. 推送代码到Git常见错误参考文档Git和SVN差异 Git是一个开源的分布式版本控制系统&#xff0c;由Linux之…

一、策略模式的使用

1、策略模式定义&#xff1a; 策略模式&#xff08;Strategy Pattern&#xff09;定义了一组策略&#xff0c;分别在不同类中封装起来&#xff0c;每种策略都可以根据当前场景相互替换&#xff0c;从而使策略的变化可以独立于操作者。比如我们要去某个地方&#xff0c;会根据距…

云原生应用配置管理的5个最佳实践

引言 在复杂的云原生应用程序中管理配置信息是非常困难的&#xff0c;似乎到处都有配置。在使用基于微服务架构的云原生应用程序中&#xff0c;配置问题成倍增加。 配置无处不在。有针对网络的配置&#xff0c;比如路由规则、端口控制、负载均衡&#xff0c;有针对数据库的配置…

JavaScript Boolean 布尔对象

文章目录JavaScript Boolean 布尔对象Boolean 对象Boolean 对象属性Boolean 对象方法检查布尔对象是 true 还是 false创建 Boolean 对象JavaScript Boolean 布尔对象 Boolean&#xff08;布尔&#xff09;对象用于将非布尔值转换为布尔值&#xff08;true 或者 false&#xff0…

CSS常用选择器

目录 1.CSS是什么 2.CSS的三种写法 2.1内部样式 2.2内联样式 2.3外部样式 3.CSS选择器 3.1标签选择器 3.2类选择器(更好的选择) 3.3ID选择器 3.4后代选择器 3.5子选择器 3.6并集选择器 3.7伪类选择器(复合选择器的特殊用法) 1.CSS是什么 CSS全称Cascding Style Sh…