C++位图—布隆过滤器

news2024/11/27 12:31:51

在这里插入图片描述

目录

  • 位图概念
    • 位图应用
  • 布隆过滤器
    • 简介
    • 布隆过滤器的优缺点
    • 布隆过滤器应用场景
    • 布隆过滤器实现
    • 布隆过滤器误判率分析
  • 总结

位图概念

在这里插入图片描述

位图是一种数据结构,用于表示一组元素的存在或不存在,通常用于大规模数据集的快速查询。它基于一个位数组(或位向量),其中每个位代表一个元素,位的值表示该元素的存在或不存在。通常来说,位图中的每个元素都与一个唯一的整数索引相关联,通过这个索引来设置或检查相应的位。

位图的主要优点包括通过映射快速判断一个元素的两种状态,例如在一堆数据里面查询某一个数存不存在,这样就只需要查看这个数的映射位置判断0和1(用0和1来表示存不存在)。通常可以在常量时间内完成,因此具有非常高的查询性能。用位图来映射大量数据可以节省大量空间,因为在位图中不要用将原始数据进行存储,只需要将该数映射到某一个比特位,用这个比特位的0和1来表示这个数据的存在与否。因此对于包含大量元素的数据集,位图通常比使用其他数据结构(如哈希表)更节省内存。

但是,位图也有自己的一些限制,例如需要固定范围的整数索引,位图适用于需要表示整数范围内的元素集合,如果索引不是整数或者需要处理非常大的范围,可能不太适合。并且不支持元素的重复,每个元素在位图中只能表示存在或不存在,不能计数重复的元素。尽管如此,位图在生活的应用还是非常的广泛,位图通常用于各种应用,包括数据库管理、网络路由表、数据压缩和位集合运算等领域,它们在需要高效的集合操作时非常有用。

位图应用

当我们遇到这样一个问题,有40亿个不重复的无符号整数,并且没有排过序。现在给一个无符号整数,如何快速判断一个数是否在这40亿个数中?如果使用整形将数据进行存储然后遍历的话,那这个内存的消耗无疑是巨大的,可以粗略的计算下需要的空间,在32位系统下40亿个无符号整数大概占据160亿个字节,约等于16G。因此使用整型进行存储不是一个好的方案,这时候使用位图就会是一个很好的解决方案。

使用一个比特位的0和1来表示这个数的存在与不存在,用这个比特位的位置来映射这个数。因为一个字节有8个比特位,在32位系统下一个整型4个字节,即32个比特位,也就是一个整型可以映射32个整数,所以用位图的话只需要用大概512M的空间即可将所有数据进行映射。根据上述思路可以定义出如下的类:

template <size_t N>
class bits_set
{
public:
	bits_set()
		:_bits(N / 8 + 1)
	{}
	~bits_set()
	{}
	void set(size_t n)
	{
		size_t i = n / 8;
		size_t j = n % 8;
		_bits[i] |= (1 << j);
	}
	void reset(size_t n)
	{
		size_t i = n / 8;
		size_t j = n % 8;
		_bits[i] &= ~(1 << j);
	}
	bool test_bits(size_t n)
	{
		size_t i = n / 8;
		size_t j = n % 8;
		return (_bits[i] >> j) & 1;
	}
private:
	vector<char> _bits;
};

在定义时可以给出数据范围,然后对象自己开辟出相应的空间。类中提供了3个方法,set方法是将数字映射的位置置1,表示该数字读取到了,reset方法是将数字映射位置置0,表示该数字删除掉了,test_bits方法表示检查该数字是否存在。如下测试:

	bits_set<10> bits;
	bits.set(5);
	bits.set(1);
	bits.set(10);
	if (bits.test_bits(10))
	{
		cout << "存在" << endl;
	}
	else
	{
		cout << "不存在" << endl;
	}
	bits.reset(10);
	if (bits.test_bits(10))
	{
		cout << "存在" << endl;
	}
	else
	{
		cout << "不存在" << endl;
	}

在这里插入图片描述
如果是40亿个整数,可以直接用0xFFFFFFFF或者-1来表示,如下:

bits_set<0xFFFFFFFF> bits;
//bits_set<-1> bits;

在这里插入图片描述

布隆过滤器

在这里插入图片描述

简介

布隆过滤器(Bloom Filter)是由布隆于1970年提出的,是一种用于快速检查一个元素是否属于一个集合的概率型数据结构。它的设计初衷是为了解决数据库中的快速查找问题,特别是在大规模数据集合中判断一个元素是否存在。布隆过滤器的关键思想是使用多个哈希函数将元素映射到一个位数组中,通过多个哈希函数的多次映射,使得位数组中的多个位可能被设置为1。当查询一个元素时,同样的哈希函数会将该元素映射到相同的位上,如果所有对应的位都被设置为1,就认为元素可能存在。简单来说,布隆过滤器就是哈希和位图的有机结合。

在这里插入图片描述

布隆过滤器的优缺点

优点:

  • 快速查询:布隆过滤器能够以常数时间复杂度进行元素的查询操作,无需遍历整个数据集合。

  • 节省内存:相比于存储原始数据集合,布隆过滤器通常占用更少的内存空间,特别在处理大规模数据集合时,内存占用有显著优势。

  • 高效的元素排除:它可以迅速排除大部分不可能存在的元素,减轻后续查询或操作的负担,提高系统性能。

  • 保密性:布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

限制:

  • 误判率:布隆过滤器具有一定的误判率,即有可能判断一个元素存在于集合中,但实际上并不存在。误判率随着哈希函数数量和位数组大小的选择有关。

  • 不支持删除操作:一般情况下一旦元素被添加到布隆过滤器中,就无法从中删除。这对于需要支持删除操作的应用不适用。

  • 精确性限制:它不能提供精确的元素存在性检查,只能提供概率性的结果。

  • 哈希函数依赖:布隆过滤器的性能高度依赖于哈希函数的选择和质量。

总的来说,布隆过滤器在某些特定场景下是非常有用的,特别是在需要快速查询和内存效率的应用中。然而,在应用时需要谨慎考虑误判率以及不支持删除操作等限制。

布隆过滤器应用场景

因为误判率的存在,布隆过滤器适合能够容忍一些误判的场景下来使用,例如注册界面下对一些用户感知不到的并且影响不大的信息处理就可以使用布隆过滤器,例如用户昵称。
在这里插入图片描述

布隆过滤器可以对用户的昵称进行快速筛选,因为布隆过滤器判断不存在是不会出现误判的,判断存在才会出现误判,所以布隆过滤器在这些场景下进行快速筛选的应用是非常广泛的。例如:

  • 网络爬虫,用于快速排除已经爬取的网页或URL,避免重复爬取相同内容。

  • 缓存系统,用于判断某个数据是否已经在缓存中,以减少数据库或文件系统的访问次数,提高性能。

  • 防止重复提交:在Web应用中,用于防止用户重复提交表单或请求。

  • 垃圾邮件过滤:用于过滤垃圾邮件,快速排除已知的垃圾邮件。

  • 数据库查询优化:用于快速判断一个数据库中是否存在某个记录,减少昂贵的数据库查询操作。

  • 资源管理:用于管理资源池中的资源,快速判断资源是否可用。

布隆过滤器实现

因为布隆过滤器是哈希和位图的有机结合,因此可以用上述的位图代码做布隆过滤器的底层容器。除此之外还需要提供哈希函数用来计算数据的映射,如下代码:

template<size_t N, size_t X = 5, class K = string, 
	class HashFunc1 = BKDRHash, 
	class HashFunc2 = APHash,
	class HashFunc3 = DJBHash>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = X * N;
		size_t hash1 = HashFunc1()(key) % len;
		_bs.set(hash1);

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

		size_t hash3 = HashFunc3()(key) % len;
		_bs.set(hash3);
	}

	bool test(const K& key)
	{
		size_t len = X * N;
		size_t hash1 = HashFunc1()(key) % len;
		if (!_bs.test_bits(hash1))
		{
			return false;
		}

		size_t hash2 = HashFunc2()(key) % len;
		if (!_bs.test_bits(hash2))
		{
			return false;
		}

		size_t hash3 = HashFunc3()(key) % len;
		if (!_bs.test_bits(hash3))
		{
			return false;
		}
		//可能误判
		return true;
	}
private:
	bits_set<N * X> _bs;
};

这段代码定义了一个布隆过滤器类 BloomFilter,其中N是布隆过滤器的位数组大小。X是一个倍数,用于计算位数组的实际大小,即 X * N。K 是要插入和测试的元素的类型,默认为 string。HashFunc1、HashFunc2 和 HashFunc3 是哈希函数的类型,默认分别为 BKDRHash、APHash 和 DJBHash。

BKDRHash是一种简单的哈希函数,最初由Brian Kernighan和Dennis Ritchie提出。它的设计目标是快速计算字符串的哈希值,通常用于散列和哈希表中。BKDRHash的基本思想是将字符串中的每个字符都映射到一个32位整数,并通过一系列位运算来组合这些整数,最终得到字符串的哈希值。

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

APHash也称作Adler-32哈希,是一种非常快速的哈希函数,通常用于计算字符串的哈希值。它最初是Adler-32校验和算法的一部分,但后来被广泛用于哈希表和布隆过滤器等数据结构。APHash的核心思想是通过一系列位运算和异或操作来组合字符串的字符以生成哈希值。

struct APHash
{
	size_t operator()(const 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;
	}
};

DJBHash是一种哈希函数,它由Daniel J. Bernstein于1991年设计。DJBHash使用了一个常数5381作为初始哈希值,然后对字符串中的每个字符进行迭代处理。对于每个字符c,哈希值通过以下公式进行更新:hash = ((hash << 5) + hash) + c。最后,返回最终的哈希值。

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

类中的 set 函数用于将元素插入布隆过滤器,它使用三个不同的哈希函数对元素进行哈希,并将对应的位数组位置设置为 1。test 函数用于测试元素是否存在于布隆过滤器中,它同样使用三个不同的哈希函数对元素进行哈希,并检查对应的位数组位置是否为 1。如果有任何一个位置为 0,则返回 false,表示元素不在布隆过滤器中;否则,返回 true,表示元素可能存在于布隆过滤器中。(可能存在误判)bits_set<N * X> 是一个位数组,用于存储布隆过滤器的位信息。

布隆过滤器误判率分析

因为布隆过滤器的误判是不可避免的,但是可以尽量去降低误判率,为了方便查看误判率,可以写一个程序来查看,如下代码:

const size_t N = 10000;
BloomFilter<N> bf;

vector<string> v1;
string url = "https://blog.csdn.net/wzh18907434168?spm=1010.2135.3001.5343";

for (size_t i = 0; i < N; ++i) {
	v1.push_back(url + to_string(i));
}

for (const auto& str : v1) {
	bf.set(str);
}

vector<string> v2;
for (size_t i = 0; i < N; ++i) {
	std::string url = "https://blog.csdn.net/wzh18907434168?spm=1010.2135.3001.5343";
	url += to_string(999999 + i);
	v2.push_back(url);
}

size_t n2 = 0;
for (const auto& str : v2) {
	if (bf.test(str)) {
		++n2;
	}
}
cout << "相似字符串误判率: " << (double)n2 / (double)N << endl;

vector<string> v3;
for (size_t i = 0; i < N; ++i) {
	std::string url = "www.com";
	url += to_string(i + rand());
	v3.push_back(url);
}

size_t n3 = 0;
for (const auto& str : v3) {
	if (bf.test(str)) {
		++n3;
	}
}
cout << "不相似字符串误判率: " << (double)n3 / (double)N << endl;

这段代码创建了一个布隆过滤器对象 bf,并进行了相似字符串集和不相似字符串集的测试。当X设置为4的时候,误判率如下:

在这里插入图片描述
当X设置为5的时候,误判率如下:
在这里插入图片描述
当X设置为6的时候,误判率如下:
在这里插入图片描述
可以看到,当X设置的值越大误判率就越低,但也并不是X设置的越大越好,X设置的越大空间开的就越大,误判率还跟提供的哈希函数有关,具体详情可以跳转详解布隆过滤器的原理,使用场景和注意事项。

总结

文章中介绍了位图和布隆过滤器的特点,应用场景,以及实现。总的来说,位图是一种存储和处理二进制数据的高效数据结构,特别适用于需要快速集合操作和大规模数据的应用。但需要谨慎选择使用场景,因为它不适用于所有类型的数据处理需求。布隆过滤器是一种用于快速估计元素存在性的数据结构,适用于某些特定的应用场景,但需要注意其误判率和容量的控制。没有那种数据结构可以适应所有的应用场景,只有它自己适用的场景,因此,选择合适的数据结构也是非常重要的。

在这里插入图片描述

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

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

相关文章

管理经济学基本概念(二): 规模经济、需求曲线、供给曲线等

1、关键术语 1.1、边际报酬递减规律 边际报酬递减规律是指随着产出量的扩大&#xff0c;边际生产率(与增量投入要素相联系的增量产出量)最终会下降。 递增的边际生产率意味着边际成本递增。 递增的边际成本最终导致平均成本递增。 1.2、规模经济 (1) 如果长期平均成本相对…

打开泰坦陨落2找不到msvcp120.dll无法执行代码/msvcr120.dll丢失修复方法

msvcp120.dll 是 Windows 操作系统中的一个动态链接库文件&#xff0c;对于许多程序和游戏的运行起着至关重要的作用。然而&#xff0c;有时候我们可能会遇到 msvcp120.dll 丢失的情况&#xff0c;导致电脑出现各种问题。本文将详细介绍 msvcp120.dll 丢失的四种解决方法&#…

【项目】5.1阻塞和非阻塞、同步和异步 5.2Unix、Linux上的五种IO模型

5.1阻塞和非阻塞、同步和异步&#xff08;网络IO&#xff09; 典型的一次IO的两个阶段是什么&#xff1f;数据就绪和数据读写 数据就绪&#xff1a;根据IO操作的就绪状态 阻塞非阻塞 数据读写&#xff1a;根据应用程序和内核的交互方式 同步异步 陈硕&#xff1a;在处理IO的…

【小沐学Python】各种Web服务器汇总(Python、Node.js、PHP、httpd、Nginx)

文章目录 1、Web服务器2、Python2.1 简介2.2 安装2.3 使用2.3.1 http.server&#xff08;命令&#xff09;2.3.2 socketserver2.3.3 flask2.3.4 fastapi 3、NodeJS3.1 简介3.2 安装3.3 使用3.3.1 http-server&#xff08;命令&#xff09;3.3.2 http3.3.3 express 4、PHP4.1 简…

选择排序算法:简单但有效的排序方法

在计算机科学中&#xff0c;排序算法是基础且重要的主题之一。选择排序&#xff08;Selection Sort&#xff09;是其中一个简单但非常有用的排序算法。本文将详细介绍选择排序的原理和步骤&#xff0c;并提供Java语言的实现示例。 选择排序的原理 选择排序的核心思想是不断地从…

网络工程师怎么才算开窍

做网络工程师怎么样才算开窍&#xff1f;刚才有个朋友他说他希望从事网络工程师之后若干年。比如说当他到35岁的时候&#xff0c;他不希望出现跟今天现在是2023年&#xff0c;今年的这种裁员潮里面所遇到的那些主人公&#xff0c;就是技术学的也不错&#xff0c;然后工作也不错…

37 二叉树的最大深度

二叉树的最大深度 题解1 深度优先搜索&#xff08;递归弹栈&#xff09;题解2 广度优先搜索&#xff08;队列&#xff09; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 提示&#xff1a; 树中节点…

阿里云PolarDB自研数据库详细介绍_兼容MySQL、PostgreSQL和Oracle语法

阿里云PolarDB数据库是阿里巴巴自研的关系型分布式云原生数据库&#xff0c;PolarDB兼容三种数据库引擎&#xff1a;MySQL、PostgreSQL、Oracle&#xff08;语法兼容&#xff09;&#xff0c;目前提供云原生数据库PolarDB MySQL版、云原生数据库PolarDB PostgreSQL版和云原生数…

使用 Python 的多项 Logistic 回归问题

一、说明 多项逻辑回归是一种统计方法&#xff0c;用于预测两个以上类别的分类结果。当因变量是分类变量而不是连续变量时&#xff0c;它特别有用。 二、分类预测 在多项式逻辑回归中&#xff0c;模型预测属于因变量每个类别的观测值的概率。这些概率可以解释为观察结果属于每…

聊聊并发编程——原子操作类和Fork/Join框架

目录 原子操作类 实现原子性原理 保证原子性的方法 Fork/Join框架 分而治之 工作窃取算法 Fork/Join框架的设计 示例 原子操作类 线程A和线程B同时更新变量i进行操作i1,最后的结果可能i不等于3而是等于2。这是线程不安全的更新操作&#xff0c;一般我们会使用Synchron…

CCF CSP认证 历年题目自练Day18

CCF CSP认证 历年题目自练Day18 题目一 试题编号&#xff1a; 201809-1 试题名称&#xff1a; 卖菜 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述   在一条街上有n个卖菜的商店&#xff0c;按1至n的顺序排成一排&#xff0c;这…

如何保持终身学习

文章目录 2.1. 了解你的大脑2.2 学习是对神经元网络的塑造2.3 大脑的一生 3.学习的心里基础3.1 固定思维与成长思维3.2 我们为什么要学习 4. 学习路径4.1 构建知识模块4.2 大脑是如何使用注意力的4.3 提高专注力4.4 放松一下&#xff0c;学的更好4.5 巩固你的学习痕迹4.6 被动学…

amazon自养号测评:为卖家提供稳定转化率的解决方案

亚马逊作为全球最大的跨境电商平台之一&#xff0c;吸引了大量卖家进入市场。然而&#xff0c;如何提高产品的转化率&#xff0c;吸引更多买家并促使他们下单&#xff0c;对卖家来说仍然是一个关键问题。本文将分享一些亚马逊卖家可以采用的小技巧&#xff0c;帮助他们实现这一…

Nginx之动静分离解读

目录 基本概念 基本入门 location匹配顺序 补充&#xff1a;URLRewrite 基本概念 动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来&#xff0c;动静资源做好了拆分以后&#xff0c;我们就可以根据静态资源的特点将其做缓存操作&#x…

2023八股每日一题(九月份)

9月13日 Q&#xff1a;JDK、JRE、JVM之间的区别 A&#xff1a; JDK(Java SE Development Kit)&#xff0c;Java标准开发包&#xff0c;它提供了编译、运⾏Java程序所需的各种⼯具和资源&#xff0c;包括Java编译器、Java运⾏时环境&#xff0c;以及常⽤的Java类库等JRE( Java…

jQuery入门学习

jQuery框架 jQuery是一个快速的、简洁的JavaScript框架&#xff08;库&#xff09;&#xff0c;它会封装很多JavaScript中常用的功能代码&#xff0c;提供了一个简洁的JS设计模式 优化HTML文档操作&#xff08;优化DOM操作&#xff09;事件处理动画设计Ajax 要使用JQ我们需要…

MySQL进阶_3.性能分析工具的使用

文章目录 第一节、数据库服务器的优化步骤第二节、查看系统性能参数第三节、 慢查询日志第四节、 查看 SQL 执行成本第五节、 分析查询语句&#xff1a;EXPLAIN 第一节、数据库服务器的优化步骤 当我们遇到数据库调优问题的时候&#xff0c;可以按照以下流程进行分析。整个流程…

【Java每日一题】— —第十八题:求二维数组中的元素最小值及其索引。(2023.10.02)

&#x1f578;️Hollow&#xff0c;各位小伙伴&#xff0c;今天我们要做的是第十八题。 &#x1f3af;问题&#xff1a; 求二维数组中的元素最小值及其索引。 测试结果如下&#xff1a; &#x1f3af; 答案&#xff1a; int [][]anew int[3][];a[0]new int [3];a[1]new int[5…

【Leetcode】 17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&…

如何在 Wio Terminal 上运行 RT-Thread 操作系统

Wio Terminal 是 Seeed Studio 设计的一款开发套件。它基于 SAMD51 的微控制器&#xff0c;运行速度为 120MHz&#xff08;最高可达 200MHz&#xff09;&#xff0c;拥有 4MB 外部闪存和 192KB RAM&#xff0c;具有 Realtek RTL8720DN 支持的无线连接&#xff0c;同时支持蓝牙和…