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

2023-11-11 23:52
文章标签 应用 介绍 哈希 相关 yo

本文主要是介绍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左右,可能已经多的加载不进内存了,此时需要一个循环重新选择哈希函数再去分割。

后记

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

这篇关于yo!这里是哈希应用相关介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/393682

相关文章

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h