哈希应用之布隆过滤器

文章目录

  • 1.介绍
    • 1.1百度搜索
    • 1.2知乎好文
    • 1.3自身理解
  • 2.模拟实现
    • 2.1文档阅读
    • 2.2代码剖析
  • 3.误判率的研究
  • 4.布隆过滤器的应用
    • 4.1如何找到两个分别有100亿个字符串的文件的交集[只有1G内存].分别给出精确算法和近似算法
    • 4.2如何扩展BloomFilter使得它支持删除元素的操作
  • 5.整体代码

在这里插入图片描述

1.介绍

1.1百度搜索

在这里插入图片描述
在这里插入图片描述

1.2知乎好文

详解布隆过滤器的原理,使用场景和注意事项

在这里插入图片描述

1.3自身理解

有了哈希和位图 为什么还要搞一个布隆过滤器?

位图只能处理海量整形的数据 当数据为字符串类型且大量时 目前还没有学习一个可以处理的结构

布隆过滤器的思想

当数据为字符串时 数据是很难处理的 例如当大量字符串需要处理 假定每个字符串的个数都为10 那么这些字符串可能有256^10种可能存在 [ASCII表共有256种字符] 如果用位图的思想 总共能存2的32次方种状态 怎么解决? 我们想到用2个甚至更多的比特位来标识一种字符串 但是这样仍不能避免冲突

布隆过滤器的核心

  1. 布隆过滤器最大限度减少了冲突 一个数据存在可能是误判的 但一个数据不存在一定是不存在
  2. 以一个值映射多个位置的这种方法来降低误判率
  3. 一个值映射多个位置是用哈希函数来映射的 即用3个值来映射就需要3个哈希函数
  4. 哈希函数越多 误判率越低 但所用空间越大

用一个简单的场景来介绍

在生活中我们经常会遇到这样一个页面
在这里插入图片描述
在这里插入图片描述

这里输入的就是一个字符串类型 如果我们用之前的算法处理 效率太过低下 因为这个游戏要求昵称单一即不能重复 当用户输入一个昵称[字符串] 游戏要把这个字符串和王者荣耀现有的所有字符串比较一遍查看用户输入的这个字符串是否被允许使用 而王者荣耀现有用户为在这里插入图片描述
如果仅仅开始注册输入一个昵称就需要这么长时间 我想大多数人骂一句之后直接卸载 在这里插入图片描述
那怎么处理呢? 此时布隆过滤器的作用就大展身手了 布隆过滤器的查询效率是O(1)当用户输入的昵称不可用时 通过布隆过滤器可以明确的得到==[您输入的昵称不可用]==这样一个结果 但是有人问了 布隆过滤器虽然可以肯定的判断数据不存在 但是会误判存在 那怎么办?面对此种情况 大佬这样设计 当输入的数据使用布隆过滤器查询时 得到了false的结果 下一半会再到磁盘上的数据库查询以便得到确切结果 又有人说了 不如直接去数据库 一步到位 我们来回答这个问题:1.昵称查询状态是不存在即可用时 这个结果是确切的 不存在就是不存在 可以用 当昵称判断结果是存在即不可用的 即便这个结果可能误判 一个昵称 用户再输入一个就完了 2.不同的是 用户输入一个身份证号[以下简称id]时 查询不存在即可用 [这样的情况是大多数的 因为用户知道自己的id] 当查询存在时 设计者此时设计了 再去数据库查找这一步骤就可解决[这样的情况是极少见的 因为用户自己知道到底有没有用过这个id去注册 忘记的情况也很少 即便忘记 设计者也有后续解决办法即去数据库再查找 所以整体看 先使用布隆过滤器"过滤"这样的效率无疑是最优的 "布隆过滤器"的名称想必各位也心中有数]

2.模拟实现

2.1文档阅读

字符串Hash函数对比

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2代码剖析

在这里插入图片描述

在这里插入图片描述

3.误判率的研究

//测试误判率
void test_bloomfilter2()
{srand(time(0));const size_t N = 10000;BloomFilter<N> bf;  //4w个比特位v1:url1 url2 url3...url9999///vector<string> v1;string url = "https://www.gitee.com/Ape-LHR/apes-warehouse/547993.html";//v1存储内容:url1 url2 url3....url9999共N个for (size_t i = 0; i < N; ++i){v1.push_back(url + to_string(i));}//把v1里面的每个字符串插入到布隆过滤器for (auto& str : v1){bf.insert_setone(str);}/v2:url10000 url10001 url10002...url19999//v2存储和v1前缀相同 后缀不同的字符串vector<string> v2;for (size_t i = N; i < 2 * N; ++i){v2.push_back(url + to_string(i));}size_t count1 = 0;for (auto& str : v2){if (bf.judge(str))++count1;}double rate1 = (double)count1 / (double)N;cout << "相似字符串误判率  :" << rate1 * 100 << "%" << endl;
/////v3存储和v1前缀和后缀均有较大差异vector<string> v3;string url2 = "https://www.csdn.net/?spm=1001.2014.3001.4476";for (size_t i = 0; i < N; ++i){v3.push_back(url2 + to_string(i + rand()));}size_t count2 = 0;for (auto& str : v3){if (bf.judge(str))++count2;}double rate2 = (double)count2 / (double)N;cout << "不相似字符串误判率:" << rate2 * 100 << "%" << endl;
}

num增加 开的比特位更多 数据分布越分散 误判率越低 也可以通过增加哈希函数的个数[效果不佳]

在这里插入图片描述

4.布隆过滤器的应用

4.1如何找到两个分别有100亿个字符串的文件的交集[只有1G内存].分别给出精确算法和近似算法

估算大小

假设一个字符串占50字节 100亿个字符串占5000亿字节 ≈ 500G

思路

将每个文件分成1000份 每份0.5G 此时去查找交集

上述思路的问题

每一个A小文件都要去B小文件查找 共1000 * 1000次查找 效率极低

解决办法

对于A文件中的每一个字符串通过特定的函数计算出Hashi = hashfunc(string) % 1000
1000个文件即1000个小容器 字符串依据函数计算出下标 不同的下标进入不同的文件 假定A的1000个小文件分别是a0 a1 a2 a3…a999[B小文件亦然] 此时只需比较a0和b0 a1和b1…ai和bi

为什么只需比比较ai和bi?

A和B中成为交集的要求是string相同 如果string相同 因为hashfunc相同他们进入的文件编号一定相同 若要查找交集 只需查找比较编号相同的文件即可

存在问题

此时的切分不再是平均切分 通过这样的哈希切分 存在这样一个情况 a50号文件大小50M b50号文件5G

能不能将超出预期大小的文件再次进行哈希切分?

不能.因为存在这样一种情况: b50号文件大小5G 但重复字符串有4G 无论怎么二次切分 都会存在一个超出预期大小的文件

解决办法

利用unordered_set/set不可存储重复值的特点 将超出预期的小文件中的字符串依次读取
如果文件中的字符串都可以成功插入 set所能存储字节数有限 超出预期的文件都能插入成功 说明 该文件存在大量重复string
如果插入工程抛出内存异常 则该超出预期的文件中有大量非重复string 以至于没插完内存就不够了

4.2如何扩展BloomFilter使得它支持删除元素的操作

布隆过滤器不支持删除操作 因为一个位置可能是多个数据的子位
假如 x y 存在 且二者位置有交集 删除x后 y也被认为不在 而y实际存在
若非要支持删除 则可以:原来每个位是0/1 现在我们让每3个位作为一个位置
111最大可以标识7 当不存在置0 每存在一次++ 删除则-- 存在的问题 当数据量过大 7不一定能避免重复
如果继续增大 比如用4个位 1111最大标识15 但是所需空间增大 可能溢出

5.整体代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;//一个比特位变标识两种状态 0 1
template<size_t N>
class bitmap
{
public://构造函数bitmap() {//开空间 初始化成0_bits.resize(N / 8 + 1, 0);} //插入: 将数x映射的位 置成1void insert_setone(size_t x){//第i个字节  0 1 2 3 ...size_t i = x / 8;//第i个字节的第j个位size_t j = x % 8;//利用或等 第j位-置1 其余位-不变  _bits[i] |= (1 << j);  //左移:并不是向左移而是向高位移} //删除: 将数x映射的位 置成0void erase_setzero(size_t x){//第i个字节  0 1 2 3 ...size_t i = x / 8;//第i个字节的第j个位size_t j = x % 8;//利用与等 第j位-置0 其余位-不变 _bits[i] &= ~(1 << j);}//判断: 判断数x是否存在 bool judge(size_t x){//第i个字节  0 1 2 3 ...size_t i = x / 8;//第i个字节的第j个位size_t j = x % 8;//假定数x存在 那么第j位应为1//_bits[i]访问到的是 数x所在第i个字节的整体数return _bits[i] & (1 << j);}private:vector<char> _bits;
}; 测试函数 ///void test_bitmap1()
{bitmap<100> bm;bm.insert_setone(10);bm.insert_setone(11);bm.insert_setone(15);cout << bm.judge(10) << endl;cout << bm.judge(15) << endl;bm.erase_setzero(10);cout << bm.judge(10) << endl;cout << bm.judge(15) << endl;bm.erase_setzero(10);bm.erase_setzero(15);cout << bm.judge(10) << endl;cout << bm.judge(15) << endl;
}void test_bitmap2()
{//4294967295//bitset<-1> bm;bitmap<0xFFFFFFFF> bm;
}//Brian Kernighan与Dennis Ritchie《The C Programming Language》
struct BKDR_Hash
{size_t operator()(const string& s){size_t value = 0;for (auto& ch : s){value = value * 31 + ch;}return value;}
};//Arash Partow
struct AP_Hash
{size_t operator()(const string& s){size_t value = 0;for (long i = 0; i < s.size(); i++){size_t ch = s[i];if ((i & 1) == 0)value ^= ((value << 7) ^ ch ^ (value >> 3));elsevalue ^= (~((value << 11) ^ ch ^ (value >> 5)));}return value;}
};//Daniel J. Bernstein教授
struct DJB_Hash
{size_t operator()(const string& s){size_t value = 5381;for (auto ch : s){value += (value << 5) + ch;}return value;}
};template
<size_t N,               //插入数据个数class K = string,       //数据类型class Hash1 = BKDR_Hash,class Hash2 = AP_Hash,class Hash3 = DJB_Hash
>
class BloomFilter
{
public://插入void insert_setone(const K& key){size_t len = num * N;size_t hash1 = Hash1()(key) % len;_bm.insert_setone(hash1);size_t hash2 = Hash2()(key) % len;_bm.insert_setone(hash2);size_t hash3 = Hash3()(key) % len;_bm.insert_setone(hash3);//cout << hash1 << " " << hash2 << " " << hash3 << " " << endl << endl;}//判断是否存在bool judge(const K& key){//但凡一个位置没有 一定不存在size_t len = N * num;size_t hash1 = Hash1()(key) % len;if (!_bm.judge(hash1)){return false;}size_t hash2 = Hash2()(key) % len;if (!_bm.judge(hash2)){return false;}size_t hash3 = Hash3()(key) % len;if (!_bm.judge(hash3)){return false;}return true;}
private:static const size_t num = 4; //倍数bitmap<num * N> _bm;         //布隆过滤器长度≈4.33 * 数据个数
};//测试插入、判断函数
void test_bloomfilter1()
{BloomFilter<100> bf;//插入bf.insert_setone("sort");bf.insert_setone("bloom");bf.insert_setone("hello world hello bit");bf.insert_setone("test");bf.insert_setone("etst");bf.insert_setone("estt");//判断存在[有误判]cout << bf.judge("sort") << endl;cout << bf.judge("bloom") << endl;cout << bf.judge("hello world hello bit") << endl;cout << bf.judge("etst") << endl;cout << bf.judge("test") << endl;cout << bf.judge("estt") << endl;//判断不存在[精确]cout << bf.judge("ssort") << endl;cout << bf.judge("tors") << endl;cout << bf.judge("ttes") << endl;
}//测试误判率
void test_bloomfilter2()
{srand(time(0));const size_t N = 10000;BloomFilter<N> bf;  //4w个比特位v1:url1 url2 url3...url9999///vector<string> v1;string url = "https://www.gitee.com/Ape-LHR/apes-warehouse/547993.html";//v1存储内容:url1 url2 url3....url9999共N个for (size_t i = 0; i < N; ++i){v1.push_back(url + to_string(i));}//把v1里面的每个字符串插入到布隆过滤器for (auto& str : v1){bf.insert_setone(str);}/v2:url10000 url10001 url10002...url19999//v2存储和v1前缀相同 后缀不同的字符串vector<string> v2;for (size_t i = N; i < 2 * N; ++i){v2.push_back(url + to_string(i));}size_t count1 = 0;for (auto& str : v2){if (bf.judge(str))++count1;}double rate1 = (double)count1 / (double)N;cout << "相似字符串误判率  :" << rate1 * 100 << "%" << endl;
/////v3存储和v1前缀和后缀均有较大差异vector<string> v3;string url2 = "https://www.csdn.net/?spm=1001.2014.3001.4476";for (size_t i = 0; i < N; ++i){v3.push_back(url2 + to_string(i + rand()));}size_t count2 = 0;for (auto& str : v3){if (bf.judge(str))++count2;}double rate2 = (double)count2 / (double)N;cout << "不相似字符串误判率:" << rate2 * 100 << "%" << endl;
}

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

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

相关文章

pytorch中nn.DataParallel多次使用

pytorch中nn.DataParallel多次使用 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader# 定义模型 class MyModel(nn.Module):def __init__(self):super(MyModel, self).__init__()self.fc nn.Linear(10, 1)def forwa…

ROS为机器人装配激光雷达

移动机器人在环境中获取障碍物的具体位置、房间的内部轮廓等信息都是非常必要的&#xff0c;这些信息是机器人创建地图、进行导航的基础数据&#xff0c;除上面所讲的Kinect&#xff0c;还可以使用激光雷达作为这种场景应用下的传感器。 激光雷达可用于测量机器人和其他物体之间…

3.简单场景构建

在新建的项目中&#xff0c;默认存在 Main Camera 和 Directional Light两个对象。若是缺失&#xff0c;可通过选择菜单中的 Game Object->Camera 和 Geme Object->Light->Directional Light进行创建。 1.添加地形及底图 通过在Cesium面板中选择 Cesium World Terrai…

批量文件重命名软件 A Better Finder Rename 11汉化for mac

A Better Finder Rename 11是一款功能强大的文件重命名工具&#xff0c;可在Mac操作系统上使用。它提供了简单而直观的界面&#xff0c;帮助用户快速批量重命名文件和文件夹&#xff0c;提高文件管理和组织效率。 以下是A Better Finder Rename 11可能提供的一些主要功能和特点…

设计模式 - 结构型模式考点篇:适配器模式(类适配器、对象适配器、接口适配器)

目录 一、适配器模式 一句话概括结构式模式 1.1、适配器模式概述 1.2、案例 1.2.1、类适配器模式实现案例 1.2.2、对象适配器 1.2.3、接口适配器 1.3、优缺点&#xff08;对象适配器模式&#xff09; 1.4、应用场景 一、适配器模式 一句话概括结构式模式 教你将类和对…

多线程入门

1 创建线程 下面的程序&#xff0c;我们可以用它来创建一个 POSIX 线程&#xff1a; #include <pthread.h> pthread_create (myThread, attr, start_routine, arg) 在这里&#xff0c;pthread_create 创建一个新的线程&#xff0c;并让它可执行。下面是关于参数的说明…

QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样 Chapter1 QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样一、本自制虚拟键盘特点二、windows打开系统自带软键盘三、让…

3、在docker 容器中安装tomcat

&#xff11;、在服务器上查找tomcat镜像,查看前5条 docker search tomcat --limit 5​​​​​​​ 2、拉取镜像到本地 拉取官方的tomcat到本地 docker pull tomcat:9.0.34-jdk8 3、查看本地镜像 docker images |grep tomcat 4、启动tomcat 服务 使用默认配置 docker ru…

如何选择一个向量数据库:Elastic Cloud 和 Zilliz Cloud 面面观

随着以 Milvus 为代表的向量数据库在 AI 产业界越来越受欢迎&#xff0c;诸如 Elasticsearch 之类的传统数据库和检索系统也开始行动起来&#xff0c;纷纷在快速集成专门的向量检索插件方面展开角逐。 例如&#xff0c;在提供类似插件的传统数据库中&#xff0c;Elasticsearch …

VAE模型(详细推导+实例代码)

文章目录 EM算法思路E步M步直观感觉 GMM模型VAEVAE思想从GMM到VAE公式推导重参数VAE神经网络另一个视角的VAE思想为什么引入encoder为什么要重参数噪声与重建 Discrete VAE 本文会从EM算法&#xff0c;GMM模型一步一步的的推导&#xff0c;在过渡到VAE模型&#xff0c;如果有熟…

棱镜七彩参编!开源领域4项团体标准正式发布

近日&#xff0c;中电标2023年第27号团体标准公告正式发布&#xff0c;《T/CESA 1270.2-2023 信息技术 开源治理 第 2 部分&#xff1a;企业治理评估模型》、《T/CESA 1270.3-2023 信息技术 开源治理 第 3 部分&#xff1a;社区治理框架》、《T/CESA 1270.5-2023 信息技术 开源…

信创办公–基于WPS的EXCEL最佳实践系列 (单元格与行列)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;单元格与行列&#xff09; 目录 应用背景操作步骤1、插入和删除行和列2、合并单元格3、调整行高与列宽4、隐藏行与列5、修改单元格对齐和缩进6、更改字体7、使用格式刷8、设置单元格内的文本自动换行9、应用单元格样式10、插…

STM32F4X I2C LM75

STM32F4X I2C LM75 I2C协议讲解I2C接线I2C协议波形I2C起始信号I2C停止信号I2C应答信号I2C寻址I2C地址格式 I2C数据传输 LM75ALM75A介绍LM75A引脚说明LM75A地址LM75A寄存器LM75A I2C协议写配置寄存器读配置寄存器写Tos和Thyst寄存器读Tos Thyst Temp寄存器LM75A温度计算 LM75A例…

力扣(LeetCode)2578. 最小和分割(C++)

哈希集合 请读者思考&#xff0c;num拆分成num1和num2&#xff0c;要使得num1 num2最小&#xff0c;应满足两条性质&#xff1a; num1和num2位数相同&#xff0c;或最多差一位。num1和num2应按数值从小到大在num中取数。 想到统计num的位数&#xff0c;以实现性质1的需要&a…

淘宝天猫商品历史价格API接口

获取淘宝商品历史价格接口的步骤如下&#xff1a; 注册淘宝开放平台&#xff1a;首先在淘宝开放平台上注册一个账号&#xff0c;并进行登录。创建应用&#xff1a;在淘宝开放平台上创建一个应用&#xff0c;并获取该应用的App Key和App Secret&#xff0c;用于后续的接口调用。…

【数据结构】二叉树的链式结构及实现

目录 1. 前置说明 2. 二叉树的遍历 2.1 前序、中序以及后序遍历 2.2 层序遍历 3. 节点个数及高度等 4. 二叉树的创建和销毁 1. 前置说明 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在大家对二叉树结构…

区块链技术的飞跃: 2023年的数字革命

随着时代的推进和技术的不断创新&#xff0c;2023年成为区块链技术飞跃发展的一年。区块链&#xff0c;一个曾经只是数字货币领域的技术&#xff0c;现在已经逐渐渗透到各个行业&#xff0c;成为推动数字经济发展的重要力量。在这个数字革命的时代&#xff0c;我们探讨区块链技…

什么是存储服务器?

随着互联网的发展&#xff0c;越来越多的信息会在网络上暴露&#xff0c;所以企业就会更加重视数据&#xff0c;因此更加安全可靠的数据存储服务器受到了大多数人的信赖&#xff0c;今天就让小编带大家了解一下什么是存储服务器吧&#xff01; 存储服务器的含义。存储服务器是…

代理IP端口是什么意思呢?

今天&#xff0c;咱们来聊聊一个小众但很有料的话题——代理IP端口&#xff0c;它可是你纵横互联网世界的好搭子哦&#xff01; 首先&#xff0c;我们得先弄明白&#xff0c;代理IP端口是个啥? 代理IP端口就像是通往网络世界的门票&#xff0c;是你和代理服务器之间的桥梁。…

使用注解新开事务 @Transactional

使用注解新开事务 Transactional(propagation Propagation.REQUIRES_NEW)