yo!这里是哈希应用相关介绍

目录

前言

位图

模拟实现 

应用举例

布隆过滤器

模拟实现

应用举例

后记


前言

        在介绍unordered系列容器时,我们知道其底层使用的是哈希表,其实哈希是一种方法,是一种思想,哈希思想(Hashing)是一种在常数时间内完成数据插入和查找的算法思想。其基本思想是通过对数据进行一个映射函数的变换,把数据存储在一个数组中,这个数组称为哈希表。受这种思想启发,许多哈希应用应运而生,包括位图、布隆过滤器、海量数据处理等,下面我们逐一进行介绍,深度理解一下哈希这种思想,无论是在解决笔试题还是面试题都能有所帮助。

位图

        在32位机器下,一个整数最大可以表示到2^32,也就是42亿多。如果给定40亿个数,如何查找一个数是否在这40亿个数中?理想情况下,创建一个size为2^32的vector,用1标识存在,用0标识不存在,再将这40亿个数映射到vector中,查找一个数只要查看对应下标所在元素是1or0即可,但是想一下,这个vector有多大,2^32(个数)*4(int大小)Byte=16G,这是何等浪费。

        想一下,非要用一个int来记录0、1以标记存在情况吗?是不是可以考虑使用一个比特位,如果用一个比特位标记那需要多大空间呢?2^32bit=512MB,这极大地减少了内存的消耗同时又达到了目的。因此,可以在vector中存储字符,一个字符是8个比特位,使用的时候是一个比特位一个比特位的用,设计出这么一个类在处理这种海量数据上面可以说是相当的合适了,stl的位图(bitset)就是这样实现的,如下图。所谓位图,就是用每一位比特位来存放某种状态,适用于海量数据且数据无重复的场景,来判断某个数据存不存在的。具体使用不过多介绍,参考

https://cplusplus.com/reference/icon-default.png?t=N7T8https://cplusplus.com/reference/类中主要函数的使用在模拟实现时会介绍到,继续往下看!

  • 模拟实现 

        首先,通过模板参数将需要存储的个数传进来,因为我这里将成员属性vector的元素设置成了char(也可设置成int),所以在构造函数中将N个bit除以8,就是char的个数,再+1的原因是预留(比如N为6,则N/8就是0,+1才可以进行存储,多出来两个bit也没事)。

        其次,对于set,将N除以8得到第几个char,将N取模8得到此char的第几个比特位,将对应比特位【或】上1,其余【或】上0不变;对于reset,如set一样,不同在于将对应比特位【且】上0,其余位【且】上1不变;对于test,将对应比特位【且】上1得到0则当前比特位是0,得到1则当前比特位是1。

代码:

template<size_t N>
class Bitset
{
public:Bitset(){_bits.resize(N / 8 + 1, 0);}//将x位置的比特位设置成1void set(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] |= (1 << j);}//将x位置的比特位设置成0void reset(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] &= ~(1 << j);}//x位置的比特位是否为1bool test(size_t x){size_t i = x / 8;size_t j = x % 8;if ((_bits[i] & (1 << j)) == 0)return false;elsereturn true;}
private:vector<char> _bits;
};
  • 应用举例

1.给100亿个整数,找到只出现一次的整数

        因为整数范围是0~2^32-1,即最大是42亿多,所以这100亿个整数肯定存在重复。

        这里我们借助两个bitset,对应两个比特位结合起来标识不同的情况,即00表不存在;01表仅存在一个;10表存在两个及以上,将100亿个整数插入之后,查看对应比特位是01的就是只出现一次的整数。

        实现代码参考如下,twoBitset1是对此实现的一个类,成员对象包括两个位图bitset;对于set函数,若遇到00的情况说明不存在此整数,将其变成01,表示插入了一个,若遇到01说明此整数仅存在一个,将其变成10,表示再插入一个,若遇到10,说明此整数有两个或以上,无需再插入了;对于print_once_num函数,功能是同时遍历两个位图,对应比特位是01的记录下来,即为仅出现一次的整数。

代码:

template <size_t N>
class twoBitset1
{
public:void set(size_t x){bool bs1bool = _bs1.test(x);bool bs2bool = _bs2.test(x);if (bs1bool == false && bs2bool == false){_bs2.set(x);}else if (bs1bool == false && bs2bool == true){_bs1.set(x);_bs2.reset(x);}}void print_once_num(){size_t i = 0;for (i = 0; i < N; i++){if ((_bs1.test(i) == false) && (_bs2.test(i) == true))cout << i << " ";}cout << endl;}private:bitset<N> _bs1;bitset<N> _bs2;
};

 2.给两个文件,分别有100亿个整数,只有1G内存,如何找到两个文件的交集

        先想一下强调只有1G内存的意义在哪。前面提到过整数范围是0~2^32-1,也就是有2^32个整数,虽然文件里的整数有100亿个,但是说明其中肯定有重复的,映射到0~2^32-1的范围内最多也就是有2^32个,也就是2^32个bit,即2^29个byte=2^19个kb=2^9个mb,即512mb,使用两个这样的位图正好1G内存。

        这里我们就是使用两个位图,将两个文件的整数分别映射到这两个位图中,再让两个位图的对应比特位【且】一下(可以【且】到某一个位图上),然后找到此位图上比特位为1的整数就是两个文件的交集,思路很清晰,实现也不难,这里无参考代码,可以自己实现一下。

3.一个文件中有100亿个整数,有1G内存,找到出现次数不超过两次的所有整数。

         如题,不超过两次,也就是仅出现一次或出现两次的整数。其实,这道题是第一题的变形,思路跟第一个大差不差,就是需要多标识一种情况,即00标识没出现,01标识仅出现一次,10标识仅出现两次,11标识出现三次及以上,实现过程与第一题也一样,这里也不多赘述,可以自己实现一下。

布隆过滤器

        思考一下上面位图的缺点,是不是只能映射整数,如果关键字是非整数,比如浮点数、string,面对海量数据又如何处理呢,是不是可以通过哈希函数将这类关键字进行一个转换再映射到位图当中来标记数据是否存在啊,布隆过滤器(bloomfilter)就是这么操作的。布隆过滤器是一种概率性数据结构,使用多个哈希函数将同一个数据映射到位图中,可以高效地插入和查询,用来告诉某样东西一定不存在或者可能存在,如下图。

        比如说我们设计三个哈希函数来映射key,对于插入函数set,根据三个哈希函数将对应三个比特位改为1即可;对于查询函数,给一个key,也通过三个哈希函数计算出对应下标查看三个位置是否都是1,首先有一个不是1那肯定就是不存在,那三个都是1就一定存在吗?我们说不一定,因为这三个位置可能是另外一个key映射过来的,并不一定是你当前查询的key映射的,这也就是布隆过滤器为什么告诉你某样东西一定不存在或可能存在的原因,要是判断为存在,那就是可能存在也可能不存在,具体存不存在需要进一步判断,但这不是布隆过滤器的任务了。

  • 模拟实现

        先看属性,属性是一个位图,大小是ratio*N,其中N是插入元素的个数,ratio是一定的比率,详细可看详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/43263751icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/43263751再看模板参数,除了N以外,布隆过滤器需要传入一个类型,这里默认是string,因为布隆过滤器常用于处理字符串,还需要传入三个Hash函数用以处理同一个key映射到三个比特位,这里Hash函数也是针对于string,若是不同的类型,可以针对性的传入,其中针对于string的Hash函数的选取可参考各种字符串Hash函数(转) - 鸭子船长 - 博客园 (cnblogs.com)https://www.cnblogs.com/zl1991/p/11820922.htmlicon-default.png?t=N7T8https://www.cnblogs.com/zl1991/p/11820922.html这里我选择了其中的三个。

        对于插入函数set,使用Hash函数映射出三个哈希值,分别将其变成1;对于判断存在函数test,实现逻辑一样,使用位图的test函数判断是否存在,实现代码可参考下方。

代码:

#pragma once
#include <iostream>
#include <bitset>
#include <string>
#include <vector>
using namespace std;//将关键字类型默认为string,是因为布隆过滤器常用于处理字符串
template<size_t N, class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
class BloomFilter
{
public:void set(const K& key){Hash1 hash1;Hash2 hash2;Hash3 hash3;size_t i1 = hash1(key) % (ratio * N);   //注意ratio*N加上括号,否则会有优先级问题size_t i2 = hash2(key) % (ratio * N);size_t i3 = hash3(key) % (ratio * N);_bs.set(i1);_bs.set(i2);_bs.set(i3);}bool test(const K& key){Hash1 hash1;Hash2 hash2;Hash3 hash3;size_t i1 = hash1(key) % (ratio * N);   //注意ratio*N加上括号,否则会有优先级问题size_t i2 = hash2(key) % (ratio * N);size_t i3 = hash3(key) % (ratio * N);if (!_bs.test(i1))return false;   //明确不存在if (!_bs.test(i2))return false;   //明确不存在if (!_bs.test(i3))return false;   //明确不存在return true;   //可能存在(即有误判)}private:const static size_t ratio = 5;bitset<ratio* N> _bs;
};

 注意:为什么布隆过滤器没有支持reset删除函数?

        因为删除某一个key时可能会影响到其他key,eg:如下图,删除美团,百度也会受到影响

那如何扩展布隆过滤器使得支持删除?可以使用计数技术为每个比特位增加一个计数器(类似硬链接),有key映射到比特位,计数器就++,删除就--,当减到0才真正的删除,比如:

但是布隆过滤器并没有这样做,因为空间消耗更加的大了,本身的优势就被削弱了,能应用到布隆过滤器的地方也不是很需要删除操作。

  • 应用举例

          那布隆过滤器能给出某样东西一定不存在,或者可能存在,这样的数据结构能应用在什么情形呢?其实还是比较多的,有很多地方就是不需要特别的准确,只需要一个概率即可,比如说游戏的昵称存在机制、预备黑名单等。

        先说游戏的昵称存在机制,在刚开始注册游戏时需要输入一个昵称,当你输入一个已经存在的,游戏会让你重新输入,直到输入一个游戏不存在的昵称。其中可以用一个布隆过滤器实现,将所有昵称放进一个布隆过滤器,当玩家输入一个昵称时,就会到这个布隆过滤器查询,若是不存在则真是不存在,此昵称可以使用,若时是存在则是可能存在,直接让玩家重新输入一个。

        一般情况下,黑名单会放进一个数据库,当判断一个ip是不是在黑名单中时,一个个去遍历查询就很麻烦,可以在这个正式黑名单之前放一个预备黑名单,用布隆过滤器实现,查询一个ip时,若不存在则肯定不在正式黑名单中,若存在则可能存在,则需要再进入正式黑名单中遍历检查。

        再来看一个有关的面试题:给两个文件,分别有100亿个query(请求(字符串)),只有1G内存,如何找到两个文件的交集,分别给出近似算法和精确算法。

近似算法:

        将一个文件的query映射到一个布隆过滤器,遍历另外一个文件的query去查看是否存在。这样做会存在两个问题:①会有误判,因为这是布隆过滤器自身的缺陷;②得到的交集存在重复,但是这也算是达到了近似算法的要求了。

精确算法:

         精确算法要求真真切切的交集,不能有重复不能有误判,那这就不能使用布隆过滤器了,这里我们使用哈希切分的思想。假设两个文件分别是A、B,

①一次读取A中的query,根据i=Hash(query)%份数M,将此query放进名为Ai的小文件,B中的query也是如此,分别放进Bi小文件中;

②将对应i相等的Ai、Bi两个小文件加载到内存,用set去判断两个小文件的交集,然后将所有对应小文件的交集放在一起即可,如下图。

原理:A、B中相同的query会进入相同编号的小文件,避免了A中的一个query要与B中所有的query都比较一番。

份数M的选取:按照平分情况分割下的小文件大小能加载进内存的份数。比如说,若一个query10byte,100亿个query就是3000亿type=300G,平均分成1000份下来,一份就是300MB左右,可加载进内存,因此M可选1000。但是值得注意,实际上切割并不是平分,而是哈希切割,也就是可能某一份小文件大小并不是300MB左右,可能已经多的加载不进内存了,此时需要一个循环重新选择哈希函数再去分割。

后记

        从上面两种应用以及举例可以看出,哈希思想特别适合处理海量数据的情形,可以将海量的数据通过哈希中一一映射的原理分类,从而解决”是否存在“、“出现几次”问题,这种题型在面试时很大概率会被提到,希望大家能够理解这种思想,在面对各种此类型的题目时都可以不变应万变,有不理解的地方可以问在评论区大家讨论,拜拜!

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

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

相关文章

【第2章 Node.js基础】2.4 Node.js 全局对象...持续更新

什么是Node.js 全局对象 对于浏览器引擎来说&#xff0c;JavaScript 脚本中的 window 是全局对象&#xff0c;而Node.js程序中的全局对象是 global&#xff0c;所有全局变量(除global本身外)都是global 对象的属性。全局变量和全局对象是所有模块都可以调用的。Node.is 的全局…

Vue3 源码解读系列(三)——组件渲染

组件渲染 vnode 本质是用来描述 DOM 的 JavaScript 对象&#xff0c;它在 Vue 中可以描述不同类型的节点&#xff0c;比如&#xff1a;普通元素节点、组件节点等。 vnode 的优点&#xff1a; 抽象&#xff1a;引入 vnode&#xff0c;可以把渲染过程抽象化&#xff0c;从而使得组…

Oracle迁移(RAC变单机模式)

1.升级内核 systemctl stop firewalld systemctl disable firewalldrpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm yum --enablerepo"elrepo-kernel" list --showduplic…

鸿蒙原生应用开发-DevEco Studio远程模拟器的使用

使用单设备模拟器运行应用/服务 Remote Emulator支持Phone、Wearable、Tablet、TV等设备类型&#xff0c;但不同区域&#xff08;开发者帐号注册地&#xff09;支持的设备类型可能不同&#xff0c;请以实际可申请的设备类型为准。 Remote Emulator中的单设备模拟器&#xff08…

图扑智慧农业:农林牧数据可视化监控平台

数字农业是一种现代农业方式&#xff0c;它将信息作为农业生产的重要元素&#xff0c;并利用现代信息技术进行农业生产过程的实时可视化、数字化设计和信息化管理。能将信息技术与农业生产的各个环节有机融合&#xff0c;对于改造传统农业和改变农业生产方式具有重要意义。 图…

语音识别与自然语言处理(NLP):技术前沿与未来趋势

语音识别与自然语言处理&#xff08;NLP&#xff09;&#xff1a;技术前沿与未来趋势 随着科技的快速发展&#xff0c;语音识别与自然语言处理&#xff08;NLP&#xff09;技术逐渐成为人工智能领域的研究热点。这两项技术的结合&#xff0c;使得机器能够更好地理解和处理人类语…

Leetcode Hot100之六:42.接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 提示&#xff1a; n height.length 1 < n < 2 * 10^4 0 < height[i] < 10^5 思路 暴力循环&#xff1a; 原本的思路是左边界i从左到…

C语言--有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。

一.解体思路 设一个结构体数组&#xff0c;数组中包含3个元素; 每个元素中的信息应包括候选人的姓名和得票数;输入被选人的姓名&#xff0c;然后与数组元素中的“姓名”成员比较&#xff0c;如果相同&#xff0c;就给这个元素中的“得票数”成 员的值加1;输出所有元素的信息。 …

[Linux打怪升级之路]-信号的保存和递达

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保…

设计模式是测试模式咩?

设计模式和测试模式概述 软件的生命周期为什么要进行测试&#xff08;测试的目的&#xff09;&#xff1f;软件的设计模式1. **瀑布模型**3. 增量和迭代模型4. 敏捷模型5. 喷泉模型 测试模型V模型W模型 一个应用程序从出生到“死亡”会经过非常漫长的流程…… 软件的生命周期 …

从windows iso文件中提取install.wim

1、首先从微软官方下载需要的windows镜像 https://www.microsoft.com/zh-cn/software-download/windows10/ 2、在下载的iso文件右键&#xff0c;打开压缩包&#xff0c;在sources文件夹下&#xff0c;应该就可以看到install.wim了。但似乎在最新的win10版本&#xff0c;微软采…

Vue3使用vue-print-nb插件打印功能

插件官网地址https://www.npmjs.com/package/vue-print-nb 效果展示: 打印效果 根据不同的Vue版本安装插件 //Vue2.0版本安装方法 npm install vue-print-nb --save pnpm install vue-print-nb --save yarn add vue-print-nb//Vue3.0版本安装方法&#xff1a; npm install vue3…

低代码平台,业务开发的“银弹”

目录 一、为什么需要低代码平台 二、低代码平台的搭建能力 三、低代码其他能力 四、写在最后 随着互联网和信息技术的快速发展&#xff0c;各行各业都在积极拥抱数字化转型。在这个过程中&#xff0c;软件开发成为企业实现数字化转型的关键环节。然而&#xff0c;传统的软件开发…

C语言 每日一题 PTA 11.8 day14

1.矩阵A乘以B 给定两个矩阵A和B&#xff0c;要求你计算它们的乘积矩阵AB。需要注意的是&#xff0c;只有规模匹配的矩阵才可以相乘。 即若A有Ra​行、Ca列&#xff0c;B有Rb行、Cb列&#xff0c;则只有Ca与Rb​相等时&#xff0c;两个矩阵才能相乘。 输入格式&#xff1a; 输入…

【Java】IntelliJ IDEA使用JDBC连接MySQL数据库并写入数据

目录 0 准备工作1 创建Java项目2 添加JDBC 驱动程序3 创建数据库连接配置文件4 创建一个 Java 类来连接和操作数据库5 运行应用程序 在 IntelliJ IDEA 中连接 MySQL 数据库并将数据存储在数据表中&#xff0c;使用 Java 和 JDBC&#xff08;Java Database Connectivity&#xf…

Android拖放startDragAndDrop拖拽Glide加载堆叠圆角图,Kotlin(5)

Android拖放startDragAndDrop拖拽Glide加载堆叠圆角图&#xff0c;Kotlin&#xff08;5&#xff09; import android.content.ClipData import android.graphics.Canvas import android.graphics.Point import android.os.Bundle import android.util.Log import android.view.…

Echarts柱状体实现滚动条动态滚动

当我们柱状图中X轴数据太多的时候&#xff0c;会自动把柱形的宽度挤的很细&#xff0c;带来的交互非常不好&#xff0c;因此就有一个属性来解决&#xff1a;dataZoom 第一种简易的版本&#xff0c;横向滚动。 dataZoom: {show: true, // 为true 滚动条出现realtime: true, // 实…

RabbitMQ集群配置以及负载均衡配置

RabbitMQ集群配置以及负载均衡配置 环境配置集群配置安装rabbitmq启动rabbitmq开启远程登录添加用户并且授权用户添加数据存放目录和日志存放目录查看端口拷⻉erlang.cookie将mq-2、mq-3作为内存节点加⼊mq-1节点集群中查看集群状态添加一个新的队列 RabbitMq负载均衡配置-HAPr…

【ChatGLM2-6B】小白入门及Docker下部署

【ChatGLM2-6B】小白入门及Docker下部署 一、简介1、ChatGLM2是什么2、组成部分3、相关地址 二、基于Docker安装部署1、前提2、CentOS7安装NVIDIA显卡驱动1&#xff09;查看服务器版本及显卡信息2&#xff09;相关依赖安装3&#xff09;显卡驱动安装 2、 CentOS7安装NVIDIA-Doc…

自主开发刷题应用网站H5源码(无需后端无需数据库)

该应用使用JSON作为题库的存储方式&#xff0c;层次清晰、结构简单易懂。 配套的word模板和模板到JSON转换工具可供使用&#xff0c;方便将题库从word格式转换为JSON格式。 四种刷题模式包括顺序刷题、乱序刷题、错题模式和背题模式&#xff0c;可以根据自己的需求选择适合的模…