【c++】位图与布隆过滤器

news2024/11/19 0:46:30

一.位图

1.位图的概念

40 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
40 亿个数中。【腾讯】
刚开始许多同学能想到的方法有:
1. 遍历,时间复杂度 O(N)
2. 排序 (O(NlogN)) ,利用二分查找 : logN
这两种方法都有缺陷:40亿个整数,大概就是16GB。40亿个字节大概就是4GB。排序要用到数组,要开出16GB大的数组,排在数组里才能进行二分查找,但是这些数组在内存里放不下,所以排序都排不了。那只能放到磁盘上,那数据在磁盘上就不能用二分了,不支持下标,效率也慢。
回归题目的本质,现在就是想知道某个数在不在这堆数据中,现在关心的只是在与不在两种状态。
可以使用一 个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。比如:

 

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

2.位图的实现

位图核心的三个操作是setresettest

set是将数据对应的比特位置设为1,reset是将x对应的比特置0,test用来查看数据在不在位于结构里,存在返回1,不存在返回0。

set() 函数的实现原理:

 1<<j 位后变为 0000 1000 让_bits[ i ]  或等上这个一位完的数后 ,只是在要插入数据的位图结构上由0 变为1 而对其他的数据没有影响。

reset()函数的原理类似首先确定该数据对应的位图结构,让1左移j位让后取反 变为 1111 0111然后与上_bits[ i ] 结果是这一个数据对应的位图结构被置为0,而其他的位图结构不变。

test()函数

 首先还是确定数据对应的位图结构中的位置,将1左移到对应位置,然后与上_bits[ i ] 如果数据是存在的那最终就返回的是1,否则就返回的是0。

3.位图代码

#pragma once
#include <iostream>
#include <vector>
using namespace std;

namespace cyf
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bits.resize(N/8+1, 0);   // 这里最值得注意 得保证所有的数据都能存下 
		}                             // 10 个数据 10 /8 =1 但是会有两个数据没有对应的位图
                                      //所以 +1 最多浪费7个比特位  最少浪费一个比特位
		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 test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _bits[i] & (1 << j);
		}
	private:
		vector<char> _bits;
	};

	void test_bitset()
	{
		
		//bitset<-1> bs2;
		bitset<0xffffffff> bs2;
		bs2.set(10);
		bs2.set(20);
		bs2.set(30);
		cout << bs2.test(11) << endl;
		cout << bs2.test(20) << endl;
		cout << bs2.test(33) << endl;
		cout << bs2.test(52) << endl;
		cout << bs2.test(45) << endl << endl;
		bs2.reset(20);
		bs2.set(666);
		cout << bs2.test(10) << endl;
		cout << bs2.test(20) << endl;
		cout << bs2.test(3000) << endl;
		cout << bs2.test(666) << endl;
		cout << bs2.test(777) << endl;
	}
}

 4.位图结构的应用

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

100亿个数字找到只出现一次的整数,这是KV模型的统计次数,数字有三种状态:0次、1次、1次以上,。这三种状态需要用两个比特位就可以表示,分别位00代表0次,01代表1次,10代表1次以上既可以。我们可以采用两个位图来实现,复用上面所实现的位图即可解决问题

template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			if (!_bs1.test(x) && !_bs2.test(x))//00
			{
				_bs2.set(x);//01
			}
			else if (!_bs1.test(x) && _bs2.test(x))//01
			{
				_bs1.set(x);
				_bs2.reset(x);//10
			}
             else
               {
			       //10不变
               }
		}
		void PrintOnce()
		{
			for (size_t i = 0; i < N; ++i)
			{
				if (!_bs1.test(i) && _bs2.test(i))
				{
					cout << i << endl;
				}
			}
			cout << endl;
		}
	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};

	void test_twobitset()
	{
		twobitset<100> tbs;
		int a[] = { 2,3,4,56,99,55,3,3,2,2,10 };
		for (auto e : a)
		{
			tbs.set(e);
		}
		tbs.PrintOnce();
	}

二.布隆过滤器

1.布隆过滤器的概念

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

位图只能针对整型,字符串通过哈希转化成整型,再去映射,对于整型没有冲突,因为整型是有限的,映射唯一的位置,但是对于字符串来说,字符串的数量是是无限的但是整形是有限的,就会发生冲突,会发生误判。这种情况查找一个字符串不在是准确的,因为字符串转换位整形后,对应的位图结构如果没有被标记为1,那就说明这个字符串不在,且一定是准确的。但是如果一个字符串的位图结构是被标记为1的,此时这个被标记的位图结构可能就是这个字符串,当然也有可能是别的字符串和这个字符串共用的一个位图结构,此时查找在的结果就是不准确的!!!

布隆过滤器:可以降低误判率:让一个值映射多个位置,但是并不是消除误判!

 但是依旧可能存在误判: 当一个字符串与位图结构中的另一个字符串的对应的比特位正好是相等,所以在布隆过滤器只是降低了误判率并没有消除误判率。

如果布隆过滤器长度比较小,比特位很快会被占为1,误判率自然会上升,所以布隆过滤器的长度会影响误判率,理论上来说,如果一个值映射的位置越多,则误判的概率越小,但是并不是位置越多越好,空间也会消耗。所以误判率和空间大小也要有一个均衡值:

 

k是哈希函数的个数,m是布隆过滤器的长度,n是插入元素的个数。K=3,ln2 取 0.7,那么 m 和 n 的关系大概是 m =4.2n ,也就是过滤器长度应该是插入元素个数的 4 -5倍

2.代码实现过滤器

#include<string>
#include <bitset>

namespace cyf
{
	struct BKDRHash
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (auto ch : key)
			{
				hash *= 131;
				hash += ch;
			}
			return hash;
		}
	};

	struct APHash
	{
		size_t operator()(const string& key)
		{
			unsigned int hash = 0;
			int i = 0;

			for (auto ch : key)
			{
				if ((i & 1) == 0)
				{
					hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
				}
				else
				{
					hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
				}

				++i;
			}

			return hash;
		}
	};

	struct DJBHash
	{
		size_t operator()(const string& key)
		{
			unsigned int hash = 5381;

			for (auto ch : key)
			{
				hash += (hash << 5) + ch;
			}

			return hash;
		}
	};

	struct JSHash
	{
		size_t operator()(const string& s)
		{
			size_t hash = 1315423911;
			for (auto ch : s)
			{
				hash ^= ((hash << 5) + ch + (hash >> 2));
			}
			return hash;
		}
	};

	// 假设N是最多存储的数据个数
	// 平均存储一个值,开辟X个位
	template<size_t N,
		size_t X = 6,
		class K = string,
		class HashFunc1 = BKDRHash,
		class HashFunc2 = APHash,
		class HashFunc3 = DJBHash,
		class HashFunc4 = JSHash>
	class BloomFilter
	{
	public:
		void set(const K& key)
		{
			size_t hash1 = HashFunc1()(key) % (N * X);
			size_t hash2 = HashFunc2()(key) % (N * X);
			size_t hash3 = HashFunc3()(key) % (N * X);
			size_t hash4 = HashFunc4()(key) % (N * X);

			_bs.set(hash1);
			_bs.set(hash2);
			_bs.set(hash3);
			_bs.set(hash4);
		}

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

			size_t hash2 = HashFunc2()(key) % (N * X);
			if (!_bs.test(hash2))
			{
				return false;
			}

			size_t hash3 = HashFunc3()(key) % (N * X);
			if (!_bs.test(hash3))
			{
				return false;
			}

			size_t hash4 = HashFunc4()(key) % (N * X);
			if (!_bs.test(hash4))
			{
				return false;
			}

			// 前面判断不在都是准确,不存在误判
			return true; // 可能存在误判,映射几个位置都冲突,就会误判
		}

	private:
		std::bitset<N* X> _bs;
	};

3.删除

布隆过滤器一般没有删除,因为布隆过滤器判断一个元素是会存在误判,此时无法保证要删除的元素在布隆过滤器中,如果此时将位图中对应的比特位清0,就会影响到其他元素了。这时候我们只需要在每个比特位加一个计数器,当存在插入操作时,在计数器里面进行 ++,删除后对该位置进行 -- 即可。但是布隆过滤器的本来目的就是为了提高效率和节省空间,在每个比特位增加额外的计数器,空间消耗那就更多了

4.布隆过滤器的优缺点

优点

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

缺点

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

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

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

相关文章

分代回收工作流程

GC回收算法之分代回收 GC回收垃圾主要有三个算法&#xff0c;分别是标记清除&#xff0c;复制算法以及标记整理。 三种算法各有优缺点&#xff0c;其中标记清除的优点就是简单高效&#xff0c;缺点就是很容易带来内存碎片化问题。 复制算法的话&#xff0c;优点确实解决了内存碎…

springcloud-alibaba (02)nacos学习笔记

nacos 一&#xff0c;介绍1. 什么是Spring Cloud Alibaba Nacos2. Nacos的特点和优势 二&#xff0c;安装和配置三&#xff0c;创建项目第一步&#xff0c;创建父工程第二步&#xff0c;创建基础公共模块第三步&#xff0c;创建服务模块第四步&#xff0c;开启多个实例 四&…

用Python进行AB测试之T检验的案例学习【文末源码地址】

文章目录 案例背景假设检验解读一、提出假设二、检验统计量三、代码实现四、结论 源码地址 案例背景 产品经理对APP做了许多改动&#xff0c;想要通过AB测试评估一下改动后的效果如何&#xff1f; 其中有一项评估是&#xff1a;签到按钮从蓝色改为红色后&#xff0c;客户点击…

Java 成员变量与局部变量有什么区别?

节选自JavaGuide(Github 标星 134k star!「Java学习 + 面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识) 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符…

Smart HTML Elements Crack

Smart HTML Elements Crack Smart HTML Elements是一个现代的Vanilla JS和ES6库以及下一代前端框架。企业级Web组件包括可访问性功能(WAI-ARIA&#xff0c;第508/WTAG法规遵从性)、本地化、从右到左键盘导航和主题化。与Angular、ReactJS、Vue.js、Bootstrap、Meteor和任何其他…

vue编写组件 根据指定日期获取一周内所有 日期与农历日期展示 并标记当天

首先 我们要引入一下对应的第三方依赖 npm install --save chinese-lunar-calendar sass sass-loader这里 我们需要 chinese-lunar-calendar 将日期变成农历日期的工具 sass是因为 我这里为了方便 用了 sass写样式 组件代码如下 <template><headerclass "ske…

学习自动化这3个坏习惯要你命,90%测试人躺枪!

将自动化测试当成很了不起的资本&#xff0c;源于国内对Coding的崇拜 盲目的学习自动化&#xff0c;不光对你的工作没有帮助&#xff0c;可能对你的测试之路还会起反作用&#xff01; 1,为什么说盲目学习自动化可能会让你一无是处&#xff1f; 没有全面理解软件测试的基本原理…

让自己再认识一下分区吧

在上个博客中&#xff0c;我们讨论了复制 —— 即数据在不同节点上的副本&#xff0c;对于非常大的数据集&#xff0c;或非常高的吞吐量&#xff0c;仅仅进行复制是不够的&#xff1a;我们需要将数据进行 分区&#xff08;partitions&#xff09;&#xff0c;也称为 分片&#…

【Proteus仿真】| 51单片机——MAX7219 驱动数码管

系列文章 todo: 文章目录 前言1. 简单了解MAX72191.1 引脚说明1.2 寄存器说明1.2.1 选位寄存器1.2.2 BCD译码器设置寄存器1.2.3 亮度寄存器1.2.4 扫描限制寄存器1.2.5 显示测试寄存器1.2.6 关停寄存器 1.3 spi总线通信时序1.4 数据格式 2. 使用MAX7219驱动数码管2.1 驱动数码管…

【2023/05/18】TPU

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第13天。 Share The waterfall sings,"I find my song,when I find my freedom." 译文&#xff1a; 瀑布歌唱到&#xff1a;“我得到自由时便有了歌声。” I cannot tell why this heart la…

精彩直击 | 迅镭激光参展CIBF2023年电池技术盛会

5月16日&#xff0c;全球规模最大的电池、能源行业盛会——CIBF2023第十五届中国国际电池技术展览会(以下简称2023CIBF电池展)&#xff0c;在深圳国际会展中心(宝安新馆)隆重开幕! 迅镭激光携一系列新能源自动化解决方案亮相9T263展位&#xff0c;与客户分享创新技术及自动化产…

【Jmeter第四章】Jmeter添加断言(捕捉自定义错误信息)

文章目录 1、断言介绍2、Jmeter使用断言3、效果展示 1、断言介绍 前言&#xff1a;关于Jmeter中的断言&#xff0c;其实可以理解为对返回信息&#xff0c;返回代码的一种处理&#xff0c; 因为对应HTTP状态200而言&#xff0c;但我们会定义不同的返回值&#xff0c;例如 code…

【数据结构<顺序表>】C语言

前言 线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条…

linux swap交换区满了怎么办(已解决)

swap交换区满了怎么办 一、不增加交换区的方法 free -m 或free -h查看占用情况 使用如下指令来查看占用swap的前十进程 for i in $( cd /proc;ls |grep "^[0-9]"|awk $0 >100) ;do awk /Swap:/{aa$2}END{print "$i",a/1024"M"} /proc/$i…

仓库信息管理系统设计与实现

一、数据库设计 1.数据库模型设计概览 2.数据库表设计 ①depository 描述&#xff1a; 该表存储仓库的信息&#xff0c;比如仓库名称&#xff0c;仓库地址和仓库介绍 表结构&#xff1a; 序号 字段名 数据类型 主键 非空 默认值 描述 1 id INT(10) 是 是 2…

LlamaIndex 联合创始人下场揭秘:如何使用私有数据提升 LLM 的能力?

ChatGPT 的爆火证明了大型语言模型&#xff08;LLM&#xff09;在生成知识和推理方面的能力。不过&#xff0c;ChatGPT 是使用公共数据集进行预训练的模型&#xff0c;因此可能无法提供与用户业务相关的特定答案或结果。 那么&#xff0c;如何使用私有数据最大化发挥 LLM 的能力…

SpringMVC第三阶段:源码解析SpringMVC如何调用Controller目标方法

源码解析SpringMVC如何调用Controller目标方法&#xff1a; 浏览器如何访问到Controller目标方法. 1、所有请求进入时候,会先进入org.springframework.web.servlet.DispatcherServlet前端控制器的doDispatch() 方法 2 、在 1016 行 getHandler() 方法中,会通过请求的资源路径…

【51单片机】万年历功能的数字时钟+倒计时 Proteus仿真 普中板子可用

// 硬件&#xff1a;DS1302、按键、LCD1602、无源蜂鸣器 // 1、具有万年历功能的数字时钟 (本世纪100年通用)&#xff0c;能够正确的显示年、月、日、时、分、秒 // 2、按键设置时间(校时) // 3、24小时内至少可设置3个闹钟&#xff0c;并具有不同的闹钟铃声。每个闹钟可以选择…

[链表OJ题 7] 环形链表

目录 题目来源&#xff1a; 代码实现&#xff1a; 思路分析&#xff1a; 实现过程&#xff1a; 题目来源&#xff1a; 力扣 141. 环形链表 题目描述 代码实现&#xff1a; bool hasCycle(struct ListNode* head) {struct ListNode* fast head, * slow head;while (fas…

为什么更新了 DNS 记录不生效?

我们在上网时如果想要访问到另一台机器上的内容&#xff0c;通常只需要直接输入一串地址&#xff0c;例如&#xff1a;www.upyun.com&#xff0c;就能够准确访问到自己想要访问的网站。但是实际上这只是方便我们记忆的字符形式网络标识&#xff0c;真正让我们的机器和另一台机器…