布隆过滤器--极致的速度

news2024/9/20 7:50:49

前言  

        上一篇博客提到了位图,位图是十分高效的数据结构,但可惜的是只支持整型,今天这篇博客的主角是布隆过滤器,他与位图有异曲同工之妙。(不了解位图可以点击下面这篇博客快速了解)位图(bitset)--明确场景极致性能-CSDN博客)

布隆过滤器定义

        布隆过滤器与位图类似,都是对比特位进行操作,不同的是位图只对一个位置标记,但布隆过滤器可以有多个哈希函数,对多个位置标记。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

        如下图所示,当我们插入一个数的时候,把多个位置标记位1

        当我们判断当前元素在不在时,就可以依次判断是否为1即可,假如3个位置有一个为0,那么该元素一定不在,假如3个位置都为1,那么我们不能确定该元素一定在,只能时可能在。因为当前hash位置1可能是由于插入其他元素造成的,即发生哈希冲突。

        所以这个数据结构就和他的名字一样,起到了过滤器的作用,将部分滥竽充数的筛出来。

布隆过滤器使用

        位图可以标记某个整数是否存在,除非两个数相同,否则不可能出现哈希冲突。但当我们想用其他类型的变量建立映射关系时一定会发生哈希碰撞造成误判,此时我们在查询当前元素是否存在就会产生误差,如果返回不存在,那么该元素一定不存在,如果返回存在则是可能存在。

        在一些对精准度要求不严格的地方,就可以用布隆过滤器,布隆过滤器的误差律可以通过数学推导求出,然后根据数学公式可以尽可能的减小误判率。对数学过程感兴趣可以看下面博客。

布隆过滤器概念及其公式推导_布隆过滤器公式推导-CSDN博客

        在这里就直接提供最终结论了。

        我们可以直接理解,当哈希函数一定时,布隆过滤器越长冲突越少,插入越少元素,冲突越少。

布隆过滤器实现

哈希函数

        布隆过滤器一般用于字符串查找,那么我们就可以给模板默认string类型。关于字符串的Hash函数有很多种这里列举三个用的较多的Hash函数。

struct HashFuncBKDR
{
	// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The CProgramming Language》
	// 一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法累乘因子为31。
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		}
		return hash;
	}
};


struct HashFuncAP
{
	// 由Arash Partow发明的一种hash算法。  
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (size_t 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 HashFuncDJB
{
	// 由Daniel J. Bernstein教授发明的一种hash算法。 
	size_t operator()(const std::string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};

类模板

        布隆过滤器实现也比较简单,无非就是设置三次位图,一般的布隆过滤器不支持删除,只能重建。

//N 预先开辟个数  一个数分配X位
template<size_t N,
	size_t X = 5,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:

	void set(const K& key)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;
		int hs1 = h1(key)%M;
		int hs2 = h2(key)%M;
		int hs3 = h3(key)%M;

		//设置三次
		_bs.set(hs1);
		_bs.set(hs2);
		_bs.set(hs3);
	}

	//检测是否在,有误判
	bool test(const K& key)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;

		int hs1 = h1(key) % M;
		if (!_bs.test(hs1))
			return false;

		int hs2 = h2(key) % M;
		if (!_bs.test(hs2))
			return false;

		int hs3 = h3(key) % M;
		if (!_bs.test(hs3))
			return false;

		return true;
	}

private:
	static const size_t M = N * X;
	bitset<M> _bs;
};

        上述代码就实现了布隆过滤器的核心操作,总体难度是不大的。

测试

void TestBloomFilter1()
{
	srand(time(0));
	const size_t N = 1000000;
	BloomFilter<N> bf;
	//BloomFilter<N, 3> bf;
	//BloomFilter<N, 10> bf;

	std::vector<std::string> v1;
	//std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
	//std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535";
	std::string url = "猪八戒";

	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(i));
	}

	for (auto& str : v1)
	{
		bf.set(str);
	}

	// v2跟v1是相似字符串集(前缀一样),但是后缀不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		std::string urlstr = url;
		urlstr += std::to_string(9999999 + i);
		v1.push_back(urlstr);
	}

	size_t n2 = 0;
	for (auto& str : v1)
	{
		if (bf.Test(str)) // 误判
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// 不相似字符串集  前缀后缀都不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		//string url = "zhihu.com";
		string url = "孙悟空";
		url += std::to_string(i + rand());
		v1.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v1)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;

}

        由此我们便可以测出误判率。在10万数据下误判率在10%以下,

        在100万数据下误判率在10%左右,

        

源码

        头文件

#pragma once
#include<bitset>
#include<iostream>
using namespace std;



struct HashFuncBKDR
{
	// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The CProgramming Language》
	// 一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法累乘因子为31。
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		}
		return hash;
	}
};


struct HashFuncAP
{
	// 由Arash Partow发明的一种hash算法。  
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (size_t 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 HashFuncDJB
{
	// 由Daniel J. Bernstein教授发明的一种hash算法。 
	size_t operator()(const std::string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};


//N 预先开辟个数  一个数分配X位
template<size_t N,
	size_t X = 5,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:

	void set(const K& key)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;
		int hs1 = h1(key)%M;
		int hs2 = h2(key)%M;
		int hs3 = h3(key)%M;

		//设置三次
		_bs.set(hs1);
		_bs.set(hs2);
		_bs.set(hs3);
	}

	//检测是否在,有误判
	bool test(const K& key)
	{
		Hash1 h1;
		Hash2 h2;
		Hash3 h3;

		int hs1 = h1(key) % M;
		if (!_bs.test(hs1))
			return false;

		int hs2 = h2(key) % M;
		if (!_bs.test(hs2))
			return false;

		int hs3 = h3(key) % M;
		if (!_bs.test(hs3))
			return false;

		return true;
	}

private:
	static const size_t M = N * X;
	bitset<M> _bs;
};

源文件

#include"BloomFilter.h"
#include<vector>
#include<string>

void TestBloomFilter1()
{
	srand(time(0));
	const size_t N = 100;
	BloomFilter<N> bf;
	//BloomFilter<N, 3> bf;
	//BloomFilter<N, 10> bf;

	std::vector<std::string> v1;
	//std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
	//std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535";
	std::string url = "猪八戒";

	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(i));
	}

	for (auto& str : v1)
	{
		bf.set(str);
	}

	// v2跟v1是相似字符串集(前缀一样),但是后缀不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		std::string urlstr = url;
		urlstr += std::to_string(9999999 + i);
		v1.push_back(urlstr);
	}

	size_t n2 = 0;
	for (auto& str : v1)
	{
		if (bf.test(str)) // 误判
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// 不相似字符串集  前缀后缀都不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		//string url = "zhihu.com";
		string url = "孙悟空";
		url += std::to_string(i + rand());
		v1.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v1)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;

}


int main()
{
	TestBloomFilter1();

	return 0;
}

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

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

相关文章

【初阶数据结构】链表(附题)

目录 一、顺序表的问题及思考 二、单链表 2.1链表的概念及结构 2.2.单链表的实现 2.2.1.节点的定义 2.2.2.链表的打印 2.2.3.头部插入删除/尾部插入删除 a.创建节点 b.尾插 c.头插 d.尾删 e.头删 2.2.4.查找数据 2.2.5.在指定位置之前插入数据 2.2.6删除pos节点 …

rabbitmq镜像集群搭建

用到的ip地址 ip地址端口192.168.101.65&#xff08;主&#xff09;15672192.168.101.7515672192.168.101.8515672 安装erlang和rabbitmq 安装 安装三个包 yum install esl-erlang_23.0-1_centos_7_amd64.rpm -y yum install esl-erlang-compat-18.1-1.noarch.rpm -y rpm -…

联想电脑如何查看ip地址?详细介绍几种方法

随着互联网的普及和技术的飞速发展&#xff0c;IP地址已成为我们日常网络活动中不可或缺的一部分。无论是访问网站、远程办公还是进行网络游戏&#xff0c;IP地址都扮演着重要的角色。对于联想电脑用户来说&#xff0c;了解如何查看自己的IP地址是一项基本技能。虎观代理小二将…

leetcode 2221.数组的三角和

1.题目要求: 给你一个下标从 0 开始的整数数组 nums &#xff0c;其中 nums[i] 是 0 到 9 之间&#xff08;两者都包含&#xff09;的一个数字。nums 的 三角和 是执行以下操作以后最后剩下元素的值&#xff1a;nums 初始包含 n 个元素。如果 n 1 &#xff0c;终止 操作。否则…

拯救者杯OPENAIGC开发者大赛今日迎来决赛,现场179支参赛团队角逐AI桂冠

2024拯救者杯OPENAIGC开发者大赛于今日&#xff08;8月17日&#xff09;在上海中庚聚龙酒店迎来精彩决赛&#xff01; 创未来AI应用赛 拯救姬AI创意赛 AI Agent极限挑战赛 三大赛道决战巅峰 37位行业大咖评审 企业&高校179支顶尖开发团队同台竞技 挑战赛33支团队现场…

golang(gin框架)结合前端h5用户注册(演示版)

1、在宝塔上软件商店安装&#xff1a;GO语言版本管理器&#xff1b; 2、把本地文件上传到服务器端&#xff1b; 3、 4、 Go 语言官方提供了一个适合中国大陆用户使用的模块代理 goproxy.cn。你可以配置 Go 使用这个代理&#xff0c;具体操作如下&#xff1a; 临时设置代理…

【Verilog-CBB】开发与验证(5)——RS后向打拍器

引言 接上篇&#xff0c;完成了RS前向打拍器的CBB设计。RS后向打拍器的设计则主要是为了缓解ready信号的时序问题。后向打拍器是对ready以及数据信号进行寄存。 RS后向打拍器的设计逻辑是这样的&#xff0c;复位时&#xff0c;ready缓存器&#xff08;深度为1&#xff09;对上…

栈与递归——385、341、394

385. 迷你语法分析器(中等) 给定一个字符串 s 表示一个整数嵌套列表&#xff0c;实现一个解析它的语法分析器并返回解析的结果 NestedInteger 。 列表中的每个元素只可能是整数或整数嵌套列表 示例 1&#xff1a; 输入&#xff1a;s "324", 输出&#xff1a;324 解释…

8个Python实用技巧,让你的代码效率飞起来

想要成为一名Python编程高手&#xff0c;不仅需要掌握语言的基础知识&#xff0c;还需要了解一些高效编程的技巧。 一、列表推导式 列表推导式是Python中一种快速生成列表的方法&#xff0c;相比传统的循环方式&#xff0c;代码更简洁、更高效。 # 使用列表推导式生成0到9的平…

vuex刷新数据丢失解决方式(插件)

1、下载插件 npm install vuex-persistedstate --save2、引入即可 import Vue from vue import Vuex from vuex import createPersistedState from vuex-persistedstate Vue.use(Vuex)const store new Vuex.Store({state: {login: null},mutations: {setlogin(state, login) …

总结HashMap, HashTable, ConcurrentHashMap 之间的区别

HashMap, HashTable, ConcurrentHashMap都是使用同一种数据结构&#xff08;数组链表&#xff09;&#xff1b; HashMap多线程状态下是不安全的&#xff1b;HashTable和ConcurrentHashMap是在HashMap的基本的数据结构上进行优化&#xff0c;使他们在多线程下是安全的&#xff…

24/8/17算法笔记 AC算法

AC算法通常指的是Actor-Critic&#xff08;演员-评论家&#xff09;算法&#xff0c;它是强化学习中的一种算法框架&#xff0c;用于解决代理&#xff08;agent&#xff09;在环境中的决策问题。Actor-Critic方法结合了价值估计和策略优化&#xff0c;通常比纯粹的价值迭代或策…

Nginx 配置文件中 location、proxy_pass最后的斜杠/作用

1.搭建一个简单的sprigbootthymeleaf项目 写个controller RequestMapping("/login") public String toLogin() {return "login"; } 写一个login.html页面 <!DOCTYPE html> <html xmlns:th"http://www.thymeleaf.org"> <head&…

瑞吉外卖后续笔记

Git学习 下载地址 Githttps://git-scm.com/ 常用的git代码托管服务 git常用命令 Git 全局设置: git config --global user.name "刘某人" git config --global user.email "邮箱号" 查看配置 git config --list git add 文件名 添加到暂冲区 git a…

SpringBoot--05--整合WebSocket,实现全双工通信

文章目录 为什么需要websocket项目中使用websocket导入maven坐标编写配置类server代码前端代码和http请求URL区别 为什么需要websocket 传统的HTTP协议是单向通信的&#xff0c;支持客户端向服务器发送请求&#xff0c;服务器接收请求。但是服务器有时也要向客户端发送请求。 …

万能视频播放器PotPlayer

软件介绍 PotPlayer播放器是一款全能 多媒体 影音播放器,堪称Windows平台最强大的本地 视频播放器. PotPlayer最新版拥有强劲播放引擎加速,支持DXVA,CUDA,QuickSync,多媒体播放器支持蓝光3D,其内置强大的编码器及滤镜/分离器。 支持自定义添加解码器,对字幕的支持非常优秀,能…

基于CST的特征模天线设计

前言&#xff1a; 特征模理论是在矩量法基础上发展而来的适用于各种电磁辐射和散射问题分析的理论&#xff0c;它有效综合了这两类方法的长处且克服了它们的不足&#xff0c;不仅可以通过明确的物理含义来直观深刻地揭示天线的工作原理&#xff0c;而且能求解任意辐射结构的复…

谷粒商城实战笔记-193~194-商城业务-多线程-线程池

文章目录 一&#xff0c;193-商城业务-异步-异步复习1. 继承Thread类2. 实现Runnable接口3. 实现Callable接口结合FutureTask4. 使用线程池 二&#xff0c;194-商城业务-异步-线程池详解1&#xff0c;线程池七大参数2&#xff0c;面试题3&#xff0c;Executors能创建的4中线程池…

个人经历分享如何用Python日入1K+,分享兼职网站和渠道!

大部分人主要通过接私活赚钱。我第一次接单是朋友介绍的&#xff0c;当时刚学Python&#xff0c;为一家公司爬数据&#xff0c;赚了一千多。从那之后逐渐熟练&#xff0c;常在假期接一些数据处理的单&#xff0c;很多时候兼职收入是主业收入的2~3倍。 附上我前两年的兼职接单记…

黑马头条vue2.0项目实战(十一)——功能优化(组件缓存、响应拦截器、路由跳转与权限管理)

1. 组件缓存 1.1 介绍 先来看一个问题&#xff1f; 从首页切换到我的&#xff0c;再从我的回到首页&#xff0c;我们发现首页重新渲染原来的状态没有了。 首先&#xff0c;这是正常的状态&#xff0c;并非问题&#xff0c;路由在切换的时候会销毁切出去的页面组件&#xff…