【数据结构进阶】哈希的应用

在这里插入图片描述

🔥个人主页: Forcible Bug Maker
🔥专栏: C++ || 数据结构

目录

  • 🌈前言
  • 🔥位图
    • 位图的概念
    • 位图的实现
    • 位图的变体
  • 🔥布隆过滤器
    • 布隆过滤器的概念
    • 布隆过滤器的插入
    • 布隆过滤器的查找
    • 布隆过滤器的删除
    • 布隆过滤器的实现
    • 如何选择哈希函数个数和布隆过滤器长度
    • 小结
  • 🔥海量数据处理(哈希切分)
  • 🌈结语

🌈前言

本篇博客主要内容:哈希在数据存储和处理中的应用

本篇博客大致可以分为三部分,分别对应三个知识点:位图,布隆过滤器,以及海量数据处理。话不多说,开始我们本次的内容。

🔥位图

位图的概念

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

下面看一道腾讯曾出过的面试题:
给40亿个不重复的无符号整数,没排过序。给定一个无符号整数,如何快速判断一个数是否在这40亿个数中。

有三种思路供参考:

  1. 一个一个数遍历,时间复杂度 O ( N ) O(N) O(N)
  2. 排序 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N)) ,然后利用二分查找 O ( l o g N ) O(logN) O(logN)
  3. 位图解决⭐
    数据是否在给定的整型数据中,结果是在或者不在,刚好是两种状态,那么可以用一个二进制比特位来代表是否存在的信息,如果二进制比特位为1,代表存在,为0则代表不存在。如:
    在这里插入图片描述
    按照这样的方式进行设计,既能有 O ( 1 ) O(1) O(1)(位图也是一种哈希思想)的查找速度,又能节省空间来存下40亿个数的存在状态。

位图的实现

#pragma once
#include<vector>
namespace ForcibleBugMaker
{template<size_t N>class bitset{public:bitset():_bs(N / 32 + 1, 0){}// x映射的位标记为1void set(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] |= (1 << j);}// x映射的位置标记为0void reset(size_t x){size_t i = x / 32;size_t j = x % 32;_bs[i] &= (~(1 << j));}// 判断x映射的位是0还是1bool test(size_t x){size_t i = x / 32;size_t j = x % 32;return _bs[i] & (1 << j);}private:std::vector<size_t> _bs;};
}

测试一下咱们的代码:

#include<iostream>
#include"MyBitSet.h"
using namespace std;
using ForcibleBugMaker::bitset;int main()
{bitset<UINT_MAX> bs; // 我们自己实现的可以开40多亿个for (int i = 0; i < 20; i++) bs.set(i);for (int i = 5; i < 15; i++) bs.reset(i);for (int i = 0; i < 20; i++)if (bs.test(i))cout << i << " ";cout << endl;return 0;
}

在这里插入图片描述
虽然我们自己实现了bitset,但库中有原生提供的,被包在头文件#include<bitset>里。
在这里插入图片描述
如果想了解细节可以取参考相关文档:std::bitset
不过库中提供的bitset有一个缺陷,因为库里面的bitset的底层是使用静态数组实现的,所以其值无法开到INT_MAX
如图:
在这里插入图片描述
库中bitset运行结果:卡了一会,什么都没打印最后崩掉。

位图的优缺点:

  • 优点:增删查改非常快,同时节省空间
  • 缺点:只适用于整型

位图的变体

题目:
一个文件中存着100亿个整数,找出出现次数不超过2次的所有整数。
既然要判断数据存在的次数,咱们可以创建两个位图,用两个比特位表示一个数据:

  • 00:表示数据出现0次
  • 01:表示数据出现1次
  • 10:表示数据出现2次
  • 11:表示数据出现3次及以上

用类实现此数据结构,嵌套之前实现的bitset:

template<size_t N>
class twobitset
{
public:void set(size_t x){bool bit1 = _bs1.test(x);bool bit2 = _bs2.test(x);if (!bit1 && !bit2) { // 00->01_bs2.set(x);}else if (!bit1 && bit2) { // 01->10_bs1.set(x);_bs2.reset(x);}else if (bit1 && !bit2) { // 10->11_bs2.set(x);}}void reset(size_t x){bool bit1 = _bs1.test(x);bool bit2 = _bs2.test(x);if (bit1 && bit2) { // 11->10_bs2.reset(x);}else if (!bit1 && bit2) { // 01->00_bs2.reset(x);}else if (bit1 && !bit2) { // 10->01_bs1.reset(x);_bs2.set(x);}}int get_count(size_t x){bool bit1 = _bs1.test(x);bool bit2 = _bs2.test(x);if (bit1 && bit2) { // 11return 3;}else if (!bit1 && bit2) { // 01return 1;}else if (bit1 && !bit2) { // 10return 2;}else return 0;}private:bitset<N> _bs1;bitset<N> _bs2
};

这时候,将数据放入上面类创建的对象中,就可以快速找出出现次数不超过2次的数据了。

#include<iostream>
#include"MyBitSet.h"
using namespace std;
using ForcibleBugMaker::twobitset;int main()
{twobitset<100> tbs;int arr[] = { 1,33,33,33,33,55,42,42,27,27,27,24,24,24,24,5,2 };for (auto& e : arr) {tbs.set(e);}for (int i = 0; i < 100; i++) {int flag = tbs.get_count(i);if (flag == 1 || flag == 2)cout << i << ":不超过两次" << endl;else if (flag == 3)cout << i << ":大于两次" << endl;}return 0;
}

在这里插入图片描述

位图的应用:

  1. 快速查找某个数据是否在一个集合中
  2. 排序+去重
  3. 求两个集合的交集,并集等
  4. 操作系统中磁盘块标记

🔥布隆过滤器

布隆过滤器的概念

当我们在客户端刷视频的时候,它会不断给我们推荐新内容,在推荐之前,会经行去重操作。那么问题来了,这个去重操作是如何实现的?服务器记录了用户看过的内容的历史记录,当推荐视频时会从历史记录中进行筛选,过滤掉已经存在的记录。如何快速查找判呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:位图一般只能处理整型,如果视频编号是字符串,就难以处理了
  3. 将哈希和位图结合,即布隆过滤器

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

在这里插入图片描述

布隆过滤器的插入

在这里插入图片描述
向布隆过滤器中插入字符串:hello world
在这里插入图片描述
再向其中插入字符串:flower
在这里插入图片描述
一个字符串可以同时映射多个位,从而达到降低冲突的效果。

布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中

注:布隆过滤器如果判断某个元素为不存在时,该元素一定不存在;如果判断元素存在,则该元素可能存在,因为哈希函数仍然存在一定的误判,这时不可避免的。
比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。

布隆过滤器的删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素
比如:删除上图中flower元素,如果直接将该元素所对应的二进制比特位置0,hello world元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
然而使用这种设计方式依然有缺陷,不能确认元素是否真正在布隆过滤器中,还可能存在计数回绕。

布隆过滤器的实现

实现布隆过滤器并不困难,不过需要实现准备好哈希函数:

// 三种不同的字符串哈希函数
struct BKDRHash
{size_t operator()(const string& s){// BKDRsize_t value = 0;for (auto ch : s){value *= 31;value += ch;}return value;}
};
struct APHash
{size_t operator()(const string& s){size_t hash = 0;for (long i = 0; i < s.size(); i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));}}return hash;}
};
struct DJBHash
{size_t operator()(const string& s){size_t hash = 5381;for (auto ch : s){hash += (hash << 5) + ch;}return hash;}
};// 布隆过滤器
template<size_t N,size_t x = 5,class K = std::string,class Hash1 = BKDRHash,class Hash2 = APHash,class Hash3 = DJBHash>
class BloomFilter
{
public:void set(const K& key){size_t hash1 = Hash1()(key) % M;size_t hash2 = Hash2()(key) % M;size_t hash3 = Hash3()(key) % M;_bs.set(hash1);_bs.set(hash2);_bs.set(hash3);}bool test(const K& key){bool flag1 = Hash1()(key) % M;bool flag2 = Hash2()(key) % M;bool flag3 = Hash3()(key) % M;if (flag1 && flag2 && flag3)return true;else return false;}// 不支持删除,会影响其他值void reset(const K& key);
private:const size_t M = N * x;bitset<M> _bs;
};

如何选择哈希函数个数和布隆过滤器长度

如果布隆过滤器的长度不够,插入几个元素就满了,不管查找什么都会返回true,过滤器的效率此时就很低。
同时要考虑哈希函数的个数,哈希函数过多可能会导致效率低下,而哈希函数过少由可能导致哈希冲突率上升。
在这里插入图片描述

上图中:k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率

如何选择合适的k和m就很重要了,先看结果:
m = − n ∗ l n p ( l n 2 ) 2 m = -\text{\(\frac {n*lnp} {(ln2)^2}\)} m=(ln2)2nlnp
k = m n ∗ l n 2 k = \text{\(\frac m n\)}*ln2 k=nmln2

公式的推导,就使用来说并没有太大意义,这里可以简单推一下:
设k次哈希某一bit未置为1的概率是
( 1 − 1 m ) k (1-\text{\(\frac 1 m\)})^k (1m1)k
插入n个元素依然为0和为1的概率分别是
依然为0: ( 1 − 1 m ) n k (1-\text{\(\frac 1 m\)})^{nk} (1m1)nk
依然为1: 1 − ( 1 − 1 m ) n k 1-(1-\text{\(\frac 1 m\)})^{nk} 1(1m1)nk
标明某个元素是否在集合中所需的 k 个位置都按照如上的方法设置为 1,但是该方法可能会使算法错误的认为某一原本不在集合中的元素却被检测为在该集合中(False Positives),该概率由以下公式确定(当m取无穷小时,得到右式)
[ 1 − ( 1 − 1 m ) n k ] k ≈ ( 1 − e − k n / m ) k [1-(1-\text{\(\frac 1 m\)})^{nk}]^k\approx(1-e^{-kn/m})^k [1(1m1)nk]k(1ekn/m)k

注:极限公式,当 m l i m → ⁡ ∞ m\varinjlim\infin m lim时, ( 1 − 1 m ) m = e (1-\text{\(\frac 1 m\)})^m=e (1m1)m=e

小结

  • 优点:
    • 增加和查询元素的时间复杂度为: O ( K ) O(K) O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
    • 哈希函数相互之间没有关系,方便硬件并行运算
    • 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
    • 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
    • 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
    • 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
  • 缺陷:
    • 有误判率有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中
    • 不能获取元素本身
    • 一般情况下不能从布隆过滤器中删除元素
    • 如果采用计数方式删除,可能会存在计数回绕问题

🔥海量数据处理(哈希切分)

问题:
给两个文件,分别由100亿个query(查询:理解成字符串),我们只有1G内存,如何找两个文件的交集?
分析:假设每个query字符串50byte,100亿个query就是5000亿byte,约等于500G(1G约等于10亿多byte)。
这样的大小对于红黑树/哈希表来说是无能为力的。

解决方案1
使用一个布隆过滤器,将一个文件的query放进布隆过滤器,另一个文件依次查找,在的就是交集,问题是找到的交集不够准确,因为存在的值可能是误判的,但是交集一定被找到了。

解决方案2⭐
哈希切分,首先内存访问速度远远大于硬盘,大文件放到内存搞不定,我们可以考虑将大文件切成小份文件,再放入内存处理。
切分不能平均切分,因为平均切分以后,每个小文件都要依次进行暴力处理,效率还是会很低。
哈希切分,是依次读取文件中的query,i = HashFunc(query) % N,N为准备切分成的小文件的个数,使内存得以放下,query放进第i号小文件,这样A和B中相同的query算出的哈希值是一样的,就能放到相同的小文件当中,效率就提升了。
本质是相同的query在哈希切分的过程中,一定进入同一个小文件的Ai和Bi,不可能出现A中的query进入Ai,但是B中相同的query进入Bj的情况,所以对Ai和Bi求交集即可,不需要Ai和Bj求交集。(其中i和j是不同的整数)
在这里插入图片描述
哈希切分的问题是,每个小文件是不均分的,可能会导致某个小文件很大内存放不下。导致这种情况的原因有二:

  1. 小文件中大部分是同一query
  2. 这个小文件由很多不同的query组成(本质为哈希冲突过多)

针对问题一,我们可以将小文件的query放入set,进行去重。针对问题二,需要我们再换一个哈希函数对小文件进行二次切分。
所以当我们遇到大于1G的小文件时,可以放入set中找交集,如果set的insert抛出了异常(申请内存失败),那么就说明放不下的原因是情况二,对小文件进行二次切分后再对应找交集。

🌈结语

哈希的三个应用,分别是位图,布隆过滤器,以及海量数据处理。位图能以少量的空间快速存放数据的存在情况;布隆过滤器中需要注意k,m和n之间合适的比例关系;最后海量数据处理使用哈希切分的方式,将大文件分割成小文件,从而使计算机能够对其进行处理。本篇博客的内容到这里就结束了,感谢大家的支持♥

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

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

相关文章

Windows 10/11和Linux双系统用户请勿安装最新更新 否则将无法启动

据蓝点网报道&#xff0c;Windows 10/11 最新累积更新存在已知问题&#xff0c;如果你同时安装了 Linux 双系统则会在更新后导致系统无法正常启动。 启动时会出现如下报错&#xff1a; Verifiying shim SBAT data failed: Security Policy Violation.Something has gone serio…

Python 绘图入门

数据可视化的概念及意义 数据可视化有着久远的历史&#xff0c;最早可以追溯至10世纪&#xff0c;至今已经应用和发展了数百年。不知名的天文学家是已知的最早尝试以图形方式显示全年当中太阳&#xff0c;月亮和行星的位置变化的图。 图1 数据可视化的发展历程 什么是数据可视…

顶级期刊TMI论文解读┆PLHN: 用于DCE-MRI影像中乳腺肿瘤分割的原型学习引导混合网络

一、论文简介 本推文详细介绍了一篇上海理工大学健康科学与工程学院医学信息工程研究所&#xff08;以下简称为医信所&#xff09;周雷副教授课题组的最新论文《Prototype Learning Guided Hybrid Network for Breast Tumor Segmentation in DCE-MRI》&#xff0c;该论文发表在…

【Rust】使用开源项目搭建瓦片地图服务

本文通过获取在线和离线地图数据&#xff0c;使用开源Rust项目搭建瓦片地图服务&#xff0c;并使用DevExpress的MapControl控件使用自建地图服务 获取地图数据 获取地图数据有很多种方式&#xff0c;这里分别用在线和离线地图数据举例说明 在线下载瓦片地图 打开在线瓦片地…

搭建内网开发环境(一)|基于docker快速部署开发环境

引言 最近因需要搭建一套简易版的纯内网的开发环境&#xff0c;服务器采用 centos8.0&#xff0c;容器化技术采用 docker 使用 docker-compose 进行容器编排。 该系列教程分为两大类&#xff1a; 软件安装和使用&#xff0c;这类是开发环境常用的软件部署和使用&#xff0c;涉…

Oracle VM VirtualBox虚拟机内存不够用的解决方案

一、 前言 在使用Oracle VM VirtualBox虚拟机的过程中&#xff0c;随着时间的推移&#xff0c;我们会感觉我们的内存越来越不够用&#xff0c;今天就来给大家分享一下我们如何解决虚拟机内存不够用的问题。 二、解决方法 1.虚拟机碎片化整理 我们第一步要做的是碎片整理&…

VScode找python环境 (conda)

CtrlshiftP 会弹出如下框&#xff1a; 框里输入&#xff1a; Python:Select Interpreter 会得到&#xff1a;

24/8/15算法笔记 dp策略迭代 价值迭代

策略迭代&#xff1a; 策略迭代从某个策略开始&#xff0c;计算该策略下的状态价值函数。它交替进行两个步骤&#xff1a;策略评估&#xff08;Policy Evaluation&#xff09;和策略改进&#xff08;Policy Improvement&#xff09;。在策略评估阶段&#xff0c;计算给定策略下…

模电实验3 - 单电源集成运放交流耦合放大器

实验目标 学习集成运放的单电源使用。掌握交流耦合单电源集成运放放大器的测试方法。了解交流耦合单电源集成运放放大器的特点。 实验器材 ADALM2000 1kΩ 电阻 (1/4 W) x 1 10 kΩ 电阻 (1/4 W) x 1 100kΩ 电阻 (1/4 W) x 3 0.1μF电容 x 1 1μF电容 …

【深度学习】单层神经网络

单层神经网络 神经元感知机 1943年&#xff0c;心理学家McCulloch和数学家Pitts共同发表了神经网络的开山之作A Logical Calculus of the Ideas Immanent in Nervours Activity1&#xff0c;提出了神经网络的第一个数学模型——MP模型。该模型也成为了人工神经网络的基础。 神经…

7 数据存储单位,整型、浮点型、字符型、布尔型数据类型,sizeof 运算符

目录 1 数据类型的分类 2 数据存储单位 2.1 位 2.2 字节 2.3 其余单位 3 整数类型 3.1 基本介绍 3.2 整型的类型 3.2.1 整数类型多样性的原因 3.2.2 整型类型之间的相对大小关系 3.3 整型注意事项 3.4 字面量后缀 3.5 格式占位符 3.6 案例&#xff1a;声明并输出…

无参数读文件

目录 代码 代码解析 如何绕过获取flag 第一种方法(在apache中) ​编辑 第二种方法 首先获取目录下文件 代码 <?php highlight_file(__FILE__); if(; preg_replace(/[^\W]\((?R)?\)/, , $_GET[code])) { eval($_GET[code]); } ?> 代码解析 [^\W]\((?R)?…

接口优化笔记

索引 添加索引 where条件的关键自动或者order by后面的排序字段可以添加索引加速查询 索引只能通过删除新增进行修改&#xff0c;无法直接修改。 # 查看表的索引 show index from table_name; show create table table_name; # 添加索引 alter table table_name add index …

C/C++开发---全篇

1、统筹 学习目标&#xff1a; C/C、python精通。 就业匹配方向&#xff1a;专精一个领域&#xff0c;延长职业生涯。 &#xff08;1&#xff09;适配行业&#xff1b; &#xff08;2&#xff09;量化&#xff1b; &#xff08;3&#xff09;安全&#xff1b; &#xff08;4&…

华为od统一考试B卷【比赛】python实现

def split_params(param_str): return list(map(int, param_str.split(,))) def main(): # 获取输入 target_str input().strip() # 输入验证&#xff0c;拆分并转换为整数 try: m, n split_params(target_str) except ValueError: print(-1) return # 检查 M 和 …

react的pdf转图片格式上传到后端

这个东西做的我真的是头昏脑涨 主要需求是&#xff0c;upload上传pdf&#xff0c;pdf转图片格式展示&#xff0c;以图片格式上传到后端 封装了组件代码 父组件直接放就可以了 使用的插件pdfjs-dist&#xff0c;版本是 "pdfjs-dist": "2.5.207", impor…

高性能跨平台网络通信框架 HP-Socket v6.0.2

项目主页 : http://www.oschina.net/p/hp-socket开发文档 : https://www.docin.com/p-4592706661.html下载地址 : https://github.com/ldcsaa/HP-SocketQQ Group: 44636872, 663903943 v6.0.2 更新 一、主要更新 优化Linux通信组件多路复用处理架构&#xff0c;避免“惊群”问…

计算机的错误计算(六十三)

摘要 计算机的错误计算&#xff08;五十六&#xff09;探讨了大数的正切函数值的错误计算。本节讨论大数的余切函数的计算精度问题。 例1. 已知 计算 不妨用 3种方法计算。 (1) 在 Python 中利用 直接贴图&#xff1a; (2) 在 Java 中利用 若运行下列代码 import ja…

高密度互连HDI

HDI&#xff08;High Density Interconnector&#xff0c;高密度互连&#xff09;是一种先进的PCB技术&#xff0c;在有限的空间内&#xff0c;通过使用微细过孔和精细的布线来提高电路板的集成度。 特点&#xff1a; 微细过孔&#xff08;Microvias&#xff09;&#xff1a;…

城V4系列版本开源前后端uniapp代码

本文来自&#xff1a;智慧同城V4系列版本开源前后端uniapp代码 - 源码1688 应用介绍 演示地址&#xff1a;https://tongchengsaas.88881111.icu/ 账号&#xff1a;ceshi 密码&#xff1a;12345678 前端演示&#xff1a; 测试环境 php7.2mysql5.6ningx 安装拓展 ioncube&#x…