C++ - 哈希的应用

news2024/9/23 21:19:18

前面的文章中我们讲解了如何进行哈希表的构建以及使用实现的哈希表来模拟实现unordered_map,在本文中我们将继续来讲解一下哈希的应用。

位图

问题引入

首先我们来引入一个问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。

第一眼看到这个题目的时候,我们可能会有这样的解题思路:使用排序+二分查找的方法或者将这些数据放入红黑树或者哈希表中,但是这样的处理有这一个问题就是内存不够。1G的数据约有10亿byte的大小(1G = 1024MB = 1024 * 1024KB = 1024 * 1024 * 1024byte)那么40亿的整数就一共有16G的大小,这么大量的数据是不能够放入内存中的。因为我们的目的是判断数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。

位图概念

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

位图实现

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N/8 + 1, 0); // 为了防止访问越界的问题出现
	}

	void set(size_t x)
	{
		size_t i = x / 8; // 计算x映射的位置在第i个char数组的位置
		size_t j = x % 8; // 计算x映射的位置在第i个数组的第j个位置

		_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); // 运算符的优先级的问题,位运算的优先级比较低
		// (_bits[i] >> j) & 1; // 10%8 == 2
	}
private:
	vector<char> _bits;
};

按照上述的代码所示使用vector<char>类型的数组,每一个char有8个比特位我们对需要映射的数据进行映射,例如需要映射10这个数据,首先将10/8可以得到它在第几个char字符中,然后将其再模上8,就可以得到它在char字符的哪一个比特位上。这里还需要我们注意的就是之前学习的比特位是从右往左依次增大的,那么我们要将第i个字符的第j位设置为1时就需要将 1<<j 然后 | _bits[i]这样就可以得到正确的结果。同样要将映射取消就可以将_bits[i] &= ~(1<<j)。下面图中的数据转换为16进制就是8c。

位图的应用

下面我们再来看几道题目:

1. 给定100亿个整数,设计算法找到只出现一次的整数?
2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

针对第一个问题,100亿的整数,肯定是有着大量的重复,那么就需要我们开辟整数个数大小的空间,这里空间的开辟可以使用不同的方式,例如:

bitset<-1> bs; // 开辟的空间是根据数据的范围,开辟所有的整数可以通过-1的形式
bitset<0xffffffff> bs;

我们可以使用两张位图来解决第一个问题,给定两个位图进行处理同样的进行标记 00 01 10 11,出现了1次就将这两个位图的值设置为01;或着我们使用一个位图的方式,但是将开辟的空间改为四个,每两个作为一个组合进行标记。

针对第二个问题可以将其中的一个文件读进位图之中,在读取另外的一个文件,在就是存在交集,但是这样会出现一个问题就是存在重复的值需要进行去重操作,这里同样的有两种方法,方法1:将已经找到的交集的比特位归零防止二次统计;方法2:将两个文件分别读取进入两个位图进行比较。第三个问题与第一个问题是同样的解决方案。

总结一下就是位图的优点:速度快节省空间;缺点: 只能映射整形, 其他的类型如浮点数不能存储。那么这里就有新的方案 -- 布隆过滤器。

布隆过滤器

布隆过滤器概念

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

由于整数的范围比较小字符串的范围比较大,那么从小范围映射到大范围就会产生冲突布,隆过滤器降低冲突概率的方法思想就是,一个值映射一个位置容易误判,那么映射多个位置就可以降低误判率,按上图的例子,可以看出xyz分别映射在了不同的位置,假如现在有一个新的数据M的哈希字符串已经被xyz的数据置为了1,那么当我们进行查询的时候就会发现M虽然没有映射在布隆过滤器中但是在其中可以查询找得到。

对与一个数据来说如果它在布隆过滤器中那么它可能会是存在误判的,但是如果一个数据不存在其中,那么它一定是不存在的。

布隆过滤器的使用场景

例如以前在进行用户注册的时候需要我们填写称昵,这时就可以使用布隆过滤器,只要该用户名不出现在其中就可以使用,即使是出现虚假的重复也没有什么关系。再例如查询手机号是否重复,那么就可以先进行布隆过滤器的查找,如果不在就可以直接返回,如果存在,不管是什么情况,都可以去数据库中再次进行查找,这样就可以减少工作量。

布隆过滤器的实现

// N最多会插入的数据个数
// 哈希函数的个数,代表一个值映射几个位,哈希函数越多,误判率越低;但是哈希函数越多。平均的空间越多
template<size_t N, class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBRHash>

class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = _X * N; // 布隆过滤器的长度
		size_t hash1 = Hash1()(key) % len;
		_bs.set(hash1);

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

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

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

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

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

		size_t hash3 = Hash3()(key) % len;
		if (!_bs.test(hash3))
		{
			return false;
		}
	}

private:
	static const size_t _X = 4; // 布隆过滤器的长度与存放数据个数的比值
	bitset<N*_X> _bs;
};

关于布隆过滤器其中还有着一些具体的问题,读者可以通过这个链接进行更加详细的阅读

详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)

下面我们来看一道题目

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?(query就是相当与字符串,假设单个query,平均50byte,100亿个query就是5000亿byte)

按照之前的想法,我们可以将文件进行划分,例如划分成1000个小文件,两个文件分别切分成A0~A999和B0~B999,但是这样有一个问题就是我们要将每两个小文件都进行比较然后再取出交集的部分,这样的话工作量就会变得非常巨大。因此在这里需要我们使用一种名叫哈希切割的方法。

哈希切割 -- 小文件的编号 i = HashFunc(query) % 1000 ,每个query算出对应的i是多少就进入哪一个小文件。

然后,将AB两个文件都进行哈希切割,这样操作之后我们得到的小文件,是对应着的,在A0和B0中存放着进行切割之后相同结果的文件,那么在进行比较的时候只需要比较两者的同号小文件即可。相同的信息一定会存放在相同编号的小文件之中。

此时,有可能会出现新的问题就是:
        某些小文件因为不是平均切分会导致文件过大,这里又会有两种不同的情况
        1. 单个文件中有某个大量重复的query
        2. 单个文件中有大量不同的query

解决方案:使用unordered_map / set 依次读取文件query,插入set中
        1. 如果读取中整个小文件query都可以成功插入set那么就说明情况1
        2. 如果读取中整个小文件query 插入过程中异常(无内存),则是情况2,换用其他的哈希函数,再次分割,再求交集。
说明:set插入key如果已经有了返回false;如果没有内存就会抛出bad_alloc的异常

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

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

相关文章

Pyside6-第十三篇-布局(最后一章废话-理论篇)

本篇Pyside6的第十三篇&#xff0c;新知识点&#xff0c;布局。 布局的方式有5种。着重挑选几种将 QVBoxLayout&#xff08;垂直布局&#xff09;&#xff1a;按垂直方向排列小部件。 QHBoxLayout&#xff08;水平布局&#xff09;&#xff1a;按水平方向排列小部件。 QGridLay…

关于函数和变量命名

标识符命名基本要求 标识符是指用来识别某个实体的一个符号&#xff0c;在不同的应用环境下有不同的含义。 在计算机编程语言中&#xff0c;标识符是用户编程时使用的名字&#xff0c;用于给变量、常量、函数、语句块等命名&#xff0c;以建立起名称与使用之间的关系。 C语言…

jdk代理和cglib代理(实例推导)

目录 jdk代理和cglib代理&#xff08;实例推导&#xff09;jdk动态代理Cglib动态代理总结 jdk代理和cglib代理&#xff08;实例推导&#xff09; 更深层的探究jdk和cglib动态代理的原理 jdk动态代理 jdk动态代理&#xff08;简单实现&#xff09; 定义一个House的房源类型接口…

05 2024考研408-计算机组成原理第五章-中央处理器学习笔记

文章目录 前言一、CPU的功能与基本结构1.1、CPU的功能1.2、运算器与控制器需要实现功能1.3、运算器的基本结构1.3.1、基本结构构成&#xff08;七个部分&#xff09;1.3.2、各个部件详细介绍①算数逻辑运算单元②通用寄存器组&#xff08;介绍数据通路的基本结构2个&#xff09…

Python教程(1)——python环境的下载与安装

Python教程(1)——python环境的下载与安装 下面是下载并安装Python解释器的具体步骤&#xff0c;非常详细&#xff0c;保姆级别的教程&#xff0c;初学者一步一步的按照操作。 下载python运行环境 访问官方网站 在浏览器中打开Python的官方网站&#xff0c;网址为 https://…

【PyTest】玩转HTML报告:修改、汉化和优化

前言 Pytest框架可以使用两种测试报告&#xff0c;其中一种就是使用pytest-html插件生成的测试报告&#xff0c;但是报告中有一些信息没有什么用途或者显示的不太好看&#xff0c;还有一些我们想要在报告中展示的信息却没有&#xff0c;最近又有人问我pytest-html生成的报告&a…

vue中由 window.open转为二进制流下载 遇到下载之后无法打开或乱码的坑 (responseType: ‘blob‘ 无效)

我项目中 request.js文件用的是 axios请求的. 如果使用 window.open 下载的话没有太多要求了,但是安全性不行. 如果使用 二进制流的话就需要设置: responseType: blob (设置请求返回类型) function exportData(orgId, personName, gender) {return request({url: /console/e…

时钟、时钟域

1.1 时钟 时钟信号是一个按一定电压幅度&#xff0c;一定时间间隔连续发出的脉冲信号。 脉冲信号之间的时间间隔称为周期&#xff1a;在单位时间内所产生的脉冲个数称为频率&#xff0c;频率的标准计量单位是Hz&#xff08;赫兹&#xff09; 每一次时钟脉冲到来&#xff0c;芯…

yolov8-03训练自己的数据集并保存推理结果

目标&#xff1a;将推理结果保存为xyxy形式&#xff0c;并以 pkl 格式保存 主要采取了两种方式&#xff0c;一种是阅读源码&#xff0c;通过CIL的方式保存结果。 一种是在IDE内&#xff0c;通过python代码的形式。 查看推理相关的源码&#xff0c;探索保存结果的相关信息。 在…

PySide6/PyQT 之应用程序最小化到系统托盘

前言 在使用 PySide6/PyQT 时&#xff0c;应用程序默认只会在任务栏展示一个初始图标。很显然&#xff0c;这是不够人性化的。 而在使用微信时候&#xff0c; 按下键盘的Esc&#xff0c;就是隐藏窗口&#xff1b;按下键盘的快捷键 Ctrl Alt W就是显示或隐藏窗口&#xff1…

【Axure高保真原型】多选树穿梭框选择器

今天和大家分享多选树穿梭框的原型模板&#xff0c;左侧多选树选择子级选项后&#xff0c;可以在右侧看到对应的标签&#xff0c;取消选中也会删除对应标签。多选树可以通过选中或取消选中父级自动选中或取消选对应的子级&#xff0c;也可以选中或取消选子级自动反选父级。右侧…

首届“天网杯”网络安全大赛启动 | 赛宁网安提供全面技术支撑

​​6月25日&#xff0c;由中华人民共和国公安部、天津市人民政府指导&#xff0c;天津市公安局、天津市委网信办、天津市工信局、天津市滨海新区人民政府、公安部第一研究所、国家计算机病毒应急处理中心共同主办&#xff0c;南京赛宁信息技术有限公司提供全面技术支撑的首届“…

文件后缀名和MimeType的映射关系

tomcat 的源码里边有。 打开 Tomcat官网 在 Download 菜单下找一个版本&#xff0c;比如 Tomcat 9&#xff0c;点进去&#xff0c;下载源码 找到 conf 目录下的 web.xml 文件 打开&#xff0c;里边有很多 <mime-mapping> 节点就是&#xff0c;总共一千多个吧 粘出来&…

银行卡如何大批量合并转到一个excel表中?并形成结构化数据

将银行卡图片转为Excel后&#xff0c;可以更方便地进行储存、管理和查看&#xff0c;其次也可以快速地进行数据的筛选、统计处理和分析&#xff0c;以提高工作效率&#xff0c;最后&#xff0c;还可以避免手工输入数据时出现的错误&#xff0c;提高数据的准确性。总之&#xff…

FreeRTOS学习笔记—FreeRTOS移植

文章目录 一、FreeRTOS移植1.1 将FreeRTOS的源码添加到工程1.2 修改部分文件1.2.1 修改 SYSTEM 文件1.2.2 修改 usart.c 文件1.2.3 修改 delay.c 文件 二、FreeRTOS移植测试 一、FreeRTOS移植 这里以博主STM32俗称笔记系列的GPIO工程文件为例&#xff0c;学习一下如何进行Free…

c++语言 打字游戏(随机字母)

c语言 打字游戏(随机字母) 程序运行如下 按 enter 回车键 随机字母之后&#xff0c;输入 测出正确率 用时多少秒 测试完按空格键从新开始 退出系统 按 Esc键 #include <stdio.h> #include <time.h> #include <stdlib.h> #include <math.h> #includ…

Bootstrap 排版

文章目录 Bootstrap 排版标题内联子标题 引导主体副本强调缩写地址&#xff08;Address&#xff09; Bootstrap 排版 Bootstrap 使用 Helvetica Neue、 Helvetica、 Arial 和 sans-serif 作为其默认的字体栈。 使用 Bootstrap 的排版特性&#xff0c;您可以创建标题、段落、列…

解决Tomcat控制台窗口输出乱码问题

由于编码的问题&#xff0c;tomcat的控制台窗口输出的都是中文乱码&#xff0c;这明显是编码格式导致的&#xff0c;只要找到对应的编码格式修改一下就好了&#xff0c; 由于我的服务器编码是GBK&#xff0c;所有只需把输出的编码修改为GBK就行了。 936就是GBK编码。找到tomca…

EasyCVR电子地图鼠标悬停展示经纬度的技术实现

EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。平台可…

python字典学习

读取和拷贝 if __name__ __main__:print()dictInfo {1: "This is one", 2: "", 3: , 5: "This is five"}# 字典的读取assert (len(dictInfo[1]) > 0)assert (len(dictInfo[2]) < 0)assert (len(dictInfo[3]) < 0)if 4 in dictInfo:a…