C++学习记录——이십사 位图、布隆过滤器、哈希切割

news2025/1/18 21:15:11

文章目录

  • 1、位图
    • 位图应用
    • 优缺点
  • 2、布隆过滤器
    • 1、哈希函数
    • 2、删除
  • 3、哈希切割
    • 应用


本篇gitee

1、位图

先看一个题目:

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

这里可能会想到两种思路,排序+二分查找;放到哈希表或者红黑树。但是不要忽略一个重要的问题,40亿。实际换算一下,40亿个整数占用的空间四舍五入一下就是15G。15G,难道找这个数我需要先有个15G空间?这肯定不行。还可能想到一个方法,分成好几份查找,但是这并没有解决本质问题,占用空间大,假如要查找的数不止一个呢?

这个问题判断的是在不在的问题,所以没有必要将数字放进去某个结构,可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在,这也就是位图的操作。比如设置一个int类型的变量,占32个比特位,它的二进制位中所有可能的结果是2^32 - 1个,也就是大约42亿9千万多,那么就有可以存放足够的数据存在状态了。也可以把32个比特位分成4个char。

在之前Linux的博客中写到过,按位操作也就是位图。

我们用set函数用来设置为1,reset用来设置为0.传过来一个数后,我们如何判断在哪个char里?x / 8,在char中的第几个比特位? x % 8。找到就要开始设置。但是这是由几个char组成的,地址的存放方式还需要考虑,左移是低位向高位移,右移是高位向低位移动,不过不需要太担心这方面,编译器对此都有自己的做法。

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

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

另有一个test函数来看看标识过的值在不在。

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

测试代码

void test_zydset()
{
	zydset<100> zs;
	zs.set(47);
	zs.set(7);
	cout << zs.test(47) << endl;
	cout << zs.test(7) << endl;
	zs.reset(47);
	cout << zs.test(47) << endl;
	cout << zs.test(7) << endl;
}

如果要用的数字很大,比如42亿这9千万这种最大的,就可以写zydset< -1 > zs或者zydset< 0xFFFFFFFF > zs,编译器就会实际开这些空间。

template<size_t N>
class zydset
{
public:
	zydset()
	{
		_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 test(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;
		return _bits[i] & (1 << j);
	}
private:
	vector<char> _bits;
};

位图应用

100亿个数字,找其中只出现一次的数。

可以开两个位图,也可以改造一下位图,变成两位位图,两位位图就是N / 4了,原先的8变为4,一次性看两个比特位来检查状态,00就是0次,01就是1次,10就是1次以上。

借助上面写好的类,再写一个解决这个问题的类

template<size_t N>
class twozydset
{
public:
	void set(size_t x)
	{
		//00 变 01
		if (_zs1.test(x) == false && _zs2.test(x) == false)
		{
			_zs2.set(x);
		}
		//01 变 10
		else if (_zs1.test(x) == false && _zs2.test(x) == true)
		{
			_zs1.set(x);
			_zs2.reset(x);
		}
		//10则不变
	}

	void Print()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_zs2.test(i))
			{
				cout << i << endl;
			}
		}
	}
public:
	zydset<N> _zs1;
	zydset<N> _zs2;
};

给定两个文件,分别有100亿个整数,现在只有1G内存,如何找到两个文件交集?

可以创建两个位图,然后从循环,将一个个char都&&一遍,留下来的就是出现一次的。

也可以将一个文件的值读到一个位图中,再读取另一个文件,判断在不在上面位图中,在就是交集,但这样得出来的结果需要再次去重,这里的改进办法就是每次找到交集值,都将上面位图对应的值设置为0.

如果数据量大,用第一个方法更好。比如100亿时,用第一个,1亿时用第二个就行,因为第一个创建的是固定数,第二个则是有多少创建多少。

100亿个整数,1G内存,找到出现次数不超过2次的整数。那么就可以用两位位图,00为0ci,01为1次, 10为2次,11为3次。

优缺点

优点:速度快,节省空间

缺点:只能映射整形,其他类型入浮点数,string等都不能存储映射

2、布隆过滤器

针对一个字符串,假设有10个字符,每个按照ANSII码表会发现有256个,所以就是256的10次方,这时候再用上面的位图就不行了。这时候一定有很多冲突。

布隆过滤器的思路是这样,要解决全部冲突很难,也没什么好办法。布隆的做法就是降低冲突,之前的方法是一对一,一个映射一个位置,那么布隆就一个映射多个位置就可以降低误判率了。这里就一对三。

template<size_t N, class K, class Hash1, class Hash2, class Hash3>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		_zs.set(hash1);
		size_t hash2 = Hash2()(key) % N;
		_zs.set(hash2);
		size_t hash3 = Hash3()(key) % N;
		_zs.set(hash3);
	}

	bool test(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		if (!_zs.test(hash1))
		{
			return false;
		}
		size_t hash2 = Hash2()(key) % N;
		if (!_zs.test(hash2))
		{
			return false;
		}
		size_t hash3 = Hash3()(key) % N;
		if (!_zs.test(hash3))
		{
			return false;
		}
	}
private:
	zydset<N> _zs;
};

还是会用到之前的位图类。

布隆过滤器如果判断不在是准确的,但是判断在是不准确的。因为不在的话位置上一定都是0,没有任何映射过的痕迹,所以不在很准确;但是在就不确定了,因为有可能有其他的字符串映射到了这个位置。

布隆过滤器被使用在能容忍误判的场景。比如:注册时,快速判断昵称是否被使用过,对于手机在不在,可以先用布隆过滤器来判断不在,如果在那就进入数据库查找。

优点:快,节省内存
缺点:存在误判

1、哈希函数

补充完整的字符串的哈希函数,这里用的是一些成型的函数,里面经过了一些数学计算。

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

struct APHash
{
	size_t operator()(const string& s)
	{
		register size_t hash = 0;
		size_t ch;
		for (long i = 0; i < s.size(); i++)
		{
			size_t ch = s[i];
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

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

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 hash1 = Hash1()(key) % N;
		_zs.set(hash1);
		size_t hash2 = Hash2()(key) % N;
		_zs.set(hash2);
		size_t hash3 = Hash3()(key) % N;
		_zs.set(hash3);
	}

	bool test(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		if (!_zs.test(hash1))
		{
			return false;
		}
		size_t hash2 = Hash2()(key) % N;
		if (!_zs.test(hash2))
		{
			return false;
		}
		size_t hash3 = Hash3()(key) % N;
		if (!_zs.test(hash3))
		{
			return false;
		}
	}
private:
	zydset<N> _zs;
};

void test_bloomfilter()
{
	BloomFilter<100>;
}

在这里插入图片描述

哈希函数个数,代表一个值映射几个位,函数越多,误判率越低,消耗空间越多。k和m的平衡看下图

在这里插入图片描述

按照我们的代码使用三个哈希函数,所以k == 3,算出m / n是4多点,所以修改一下代码,一次性开4倍的空间。

class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = N * _X;
		size_t hash1 = Hash1()(key) % len;
		_zs.set(hash1);
		size_t hash2 = Hash2()(key) % len;
		_zs.set(hash2);
		size_t hash3 = Hash3()(key) % len;
		_zs.set(hash3);

		cout << hash1 << " " << hash2 << " " << hash3 << endl;
	}

	bool test(const K& key)
	{
		size_t len = N * _X;
		size_t hash1 = Hash1()(key) % len;
		if (!_zs.test(hash1))
		{
			return false;
		}
		size_t hash2 = Hash2()(key) % len;
		if (!_zs.test(hash2))
		{
			return false;
		}
		size_t hash3 = Hash3()(key) % len;
		if (!_zs.test(hash3))
		{
			return false;
		}
	}
private:
	static const size_t _X = 4;
	zydset<N * _X> _zs;
};

测试代码

void test_bloomfilter()
{
	BloomFilter<100> zs;
	zs.set("sort");
	zs.set("bloom");
	zs.set("string");
	zs.set("test");
	zs.set("etst");
	zs.set("estt");

	cout << zs.test("sort") << endl;
	cout << zs.test("bloom") << endl;
	cout << zs.test("string") << endl;
	cout << zs.test("test") << endl;
	cout << zs.test("etst") << endl;
	cout << zs.test("estt") << endl;
	cout << zs.test("zyd") << endl;
	cout << zs.test("int") << endl;
	cout << zs.test("float") << endl;
}
//测试误判率
void test_bloomfilter2()
{
	srand(time(0));
	const size_t N = 10000;
	BloomFilter<N> bf;
	vector<string> v1;
	string url = "https://blog.csdn.net/kongqizyd146?spm=1011.2415.3001.5343";
	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + to_string(i));
	}
	for (auto& str : v1)
	{
		bf.set(str);
	}
	vector<string> v2;//v2和v1相似,但不一样
	for (size_t i = 0; i < N; ++i)
	{
		string url = "https://blog.csdn.net/kongqi146?spm=1011.2415.3001.5343";
		url += to_string(999999 + i);
		v2.push_back(url);
	}
	size_t n2 = 0;
	for (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)
	{
		string url = "zhihu.com";
		url += to_string(i + rand());
		v3.push_back(url);
	}
	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率: " << (double)n3 / (double)N << endl;
}

在这里插入图片描述

十万个数据

在这里插入图片描述

可以控制映射多少个位置来控制误判率。

2、删除

删除函数不能直接删除,可以用计数的思路,用比特位来当作计数,1个比特位代表2个,2个比特位代表4个,不过呢实际上布隆过滤器不支持删除,也就不用考虑删除了。

3、哈希切割

两个文件,分别有100亿个query,现在还有1G内存,如何找到文件交集?给出精确算法和近似算法。query看作字符串。

假设查找一个字符串需要50个字节,100亿就是5千亿字节,大概是466G,两个文件就是932G。

这里的解决思路就是一个哈希切分i = HashFunc(query) % 1000;HashFunc用一种哈希函数即可。每个query算出来的i是多少,就进入Ai号小文件,另一个文件则是放入Bi号小文件。这里的前提是把两个文件各自分成若干个小文件,然后A1和B1找交集,A2和B2找交集,最后得到的就是总体的交集。文件A和B中相同的query会进入编号相同的小文件。

这个方法还有点缺陷。因为每个字符串的长度不同,每个小文件的大小控制不了都相同,也就是出现了冲突,而且可能换哈希函数也不一定解决得了问题。

单个文件中,有某个大量重复的query
单个文件中,有大量不同的query

第一个情况,重复的那些不需要再存放,所以可以使用/unordered_set/set来依次读取文件query,插入set中。

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

set插入如果已经存在,返回false,如果没有内存,会抛bad_alloc异,剩下的都会置空。

应用

给一个超过100G大小的log file,log中存着IP地址,设计算法找到出现次数最多的IP地址,以及top K的IP。

这里就是哈希切割,分成比如500个小文件,依次读取数据,i = HashFunc(ip) % 500,这个ip就是第i个小文件;依次小文件,使用map统计IP出现次数;统计过程中,出现抛内存异常,说明单个小文件过大,冲突太多,需要换哈希函数,再次哈希切割这个文件;如果没有抛异常,则正常统计,统计完一个就记录最大的。清理map,再去找下一个小文件。找top k,就把每个得到的最大值放到堆里。

相同的IP一定进入相同的小文件,读取单个小文件,就可以统计IP出现次数。

结束。

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

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

相关文章

《面试1v1》线程池

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a; 你好&#xff0c;很高兴见到你。请问你对线程池有什么了解&#xff1f; 候选人&#xff1a; 你好&#xff0c;我对线程池非常了解。线程池是一种…

前后端联调统一校验规则

文章目录 统一校验实现1.什么是统一校验2.统一校验的实现&#xff08;1&#xff09;引入依赖&#xff08;2&#xff09;基于注解&#xff08;3&#xff09;使用案例【1】定义校验规则【2】开启校验【3】统一异常处理器捕获校验产生的异常 3.分组校验(1)定义公共的校验分组(2)定…

完全自主研发,聚芯微发布3D dToF图像传感器芯片!

日前&#xff0c;由中国半导体行业协会IC设计分会&#xff08;ICCAD&#xff09;、芯原股份、松山湖管委会主办的主题为“AR/VR/XR元宇宙”的“2023松山湖中国IC创新高峰论坛”正式在广东东莞松山湖召开。武汉市聚芯微电子有限责任公司发布了完全自主知识产权的3D dToF图像传感…

Spring面试整理

什么是Spring&#xff1f; Spring的优缺点&#xff1f; Spring的模块组成 Spring框架中使用了哪些设计模式&#xff1f; 详细讲解下核心容器&#xff08;Spring context&#xff09;模块 Spring框架中有哪些不同类型的组件 Spring控制反转&#xff08;IOC&#xff09; 什…

由浅入深Dubbo核心源码剖析SPI机制

目录 1 SPI的概述1.1 SPI的主要作用1.2 入门案例1.3 总结 2 Dubbo中的SPI2.1 概述2.2 入门案例2.3 源码分析 3 SPI中的IOC和AOP3.1 依赖注入3.2 动态增强 4 动态编译4.1 SPI中的自适应4.2 javassist入门4.3 源码分析 1 SPI的概述 在 Dubbo 中&#xff0c;SPI 是一个非常重要的模…

Spring Boot 3.x 系列【35】服务监控 | 健康信息

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot版本3.0.5 源码地址:https://gitee.com/pearl-organization/study-spring-boot3 文章目录 1. 配置2. 基本原理3. 自动配置4. 自定义健康指标5. 分组6. 数据源1. 配置 Health是健康的意思,该端点用来检查正在运行…

XDP入门--BPF程序如何转发报文到其它网卡

本文目录 1、测试环境&#xff1a;2、实现的功能&#xff0c;使用bpf_redirect直接转发收到的报文到另外一张网卡3、测试步骤与测试结果 1、测试环境&#xff1a; 参照把树莓派改造成无线网卡(3)-----共享无线网络&#xff0c;无线网络转换成有线网络&#xff0c;让有线网络设…

从零实现一个数据库(DataBase) Go语言实现版 3.B树: 思路

英文源地址 关于B树和二叉查找树的直觉 我们的第一个直觉来自于平衡二叉树(BST).二叉树是用于排序数据的常用数据结构.在插入或移除键后保持树的良好形状就是’平衡’的意思.如前一章所述, 为了利用"页"(IO的最小单元), 应该使用n叉树而不是二叉树. b树可以由二叉查…

有哪些值得推荐的科研检索平台?

有哪些值得推荐的检索和笔记平台&#xff1f; 有哪些值得推荐的检索平台&#xff1f;文献检索&#xff0c;调研的推荐1. arXiv相关信息介绍推荐功能及用法 2. Web of Science相关信息介绍推荐功能及用法 3. Google Scholar相关信息介绍推荐功能及用法 4. Sci-Hub相关信息介绍 5…

Mybatis中动态sql的使用

文章目录 1. if 标签2.choose、when、otherwise3. trim、where、set4. foreach 动态 SQL 是 MyBatis 的强大特性之一,使用动态 SQL 并非一件易事&#xff0c;但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言&#xff0c;MyBatis 显著地提升了这一特性的易用性。Mbatis-P…

SaaS企业应该如何建立稳固的客户关系?

近年来&#xff0c;“客户成功”一词越来越热&#xff0c;这主要是由于当下企业正面临人口红利触顶、获客成本高昂、用户转化率低下、企业业绩增长受阻等问题&#xff0c;所以更多的企业开始将重心转移到对老客户的维护上&#xff0c;这也使得客户成功团队在企业中发挥的作用越…

Go中的异常处理

Go 中异常处理 主要掌握 一下几个方面: 掌握error接口掌握defer延迟掌握panic及recover error接口 error是指程序中出现不正常的情况,从而导致程序无法正常运行; go中为错误的类型提供了简单的错误处理机制 go中error的源码: // The error built-in interface type is t…

栈:程序员必备的工具箱

栈的结构和基本操作 本篇博客会讲解栈。栈是一种线性的数据结构&#xff0c;它满足“后进先出”的特性。栈有两端&#xff0c;分别是栈顶和栈底。每次插入或者删除数据&#xff0c;都是在栈顶的方向进行的。画个图解释一下&#xff1a;假设上面是栈顶&#xff0c;下面是栈底。…

linux ioctl 理解

背景 传统的操作系统可以分成两层&#xff0c;用户层和内核层。内核代码处理敏感资源同时在不同应用程序中间提供了安全且可信的隔离&#xff0c;出于此&#xff0c;操作系统要阻止用户态的程序直接访问内核资源。用户空间的程序通常发出一个给内核的请求&#xff0c;该请求称为…

基于vite4+pinia2模仿chatgpt移动端聊天模板Vue3MobileGPT

运用vite4.x构建mobile端仿chatgpt聊天实例Vue3-mobileGPT vue3-mobilegpt 基于 vite4vue3pinia2vue-routervant 等技术开发移动端仿ChatGPT智能聊天项目模板。支持lightdark两种主题&#xff0c;搭配vue3组件库Vant&#xff0c;界面简洁美观。 就前几天OpenAI就推出了IOS版Cha…

从 Vue Devtools 调用 WebStorm 打开文件

从 Vue Devtools 调用 WebStorm 打开文件 Vue Devtools 有一个功能, 可以直接在查看组件时, 直接打开对应的文件, 但默认是使用 VSCode 打开, 本文介绍如何使用 WebStorm 打开文件. 修改 vue.config.js: const openInEditor require("launch-editor-middleware");…

外包工作6年,聊一下感想.....

我不知道当年怎么想的&#xff0c;能在一个外包公司一干就是6年&#xff0c;后来终于跳出来了&#xff0c;现在的公司虽然不是什么大厂吧&#xff0c;但至少是个正经的互联网企业&#xff0c;待遇也不错。其实很多地方的朋友都有提到外包公司的一些弊端。 我个人的建议是&#…

linux下安装google谷歌浏览器

前言 记录下linux下安装谷歌浏览器全过程。 一、下载安装包 https://www.google.cn/intl/zh-CN/chrome/ 访问谷歌浏览器&#xff0c;拉到最下面 点击其他平台&#xff0c;选择linux 然后下载下来 下载完成后得到一个安装包 二、安装步骤 2.1.上传到linux服务器&#x…

chatgpt赋能Python-python_calu

Python Calu&#xff1a;Python程序员不可或缺的计算工具 作为一名有10年Python编程经验的工程师&#xff0c;我一直在使用Python编写各种程序&#xff0c;其中不可或缺的就是Python Calu。在下面&#xff0c;我将向您介绍Python Calu的特点及其在Python编程中的重要性。 什么…

uniapp内使用 mescroll

前言 在使用uniapp开发项目的过程中&#xff0c;在很多场景里都需要下拉刷新和上拉加载&#xff0c;而 mescroll.js 则是一个非常精致的下拉刷新和上拉加载 js 框架。 官网地址&#xff1a;mescroll 介绍 mescroll.js 是在 H5端 运行的下拉刷新和上拉加载插件&#xff0c;时…