哈希第五弹:哈希应用之布隆过滤器

前言:

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

如何我们使用哈希表的话,如果太多的新闻,所需要的内存也是个巨大的问题

但是我们要使用位图的话,哈希冲突就是一个不可避免的问题!

由于推送新闻,我们允许有一定的错误发生!

我们就可以将哈希表和位图进行结合,也就是我们本文要说的布隆过滤器

布隆过滤器

它算是一种概率型的数据结构,可以判断一个东西一定不存在或者有可能存在 。优点是插入和查询的效率,特别得高。

我们提到它是位图和哈希的重组,那怎么样重组的呢?

在位图中我们仅限于整形,使用了直接定址法确定位置;如果我们不判断整形,那么就需要使用哈希函数映射为整形,放在位图。如果我们仅存于一个位置,发生哈希冲突就会造成“双兔傍地走,安能辨我是雄雌?”的局面。

我们需要避免这种情况,怎么避免呢?如果使用多个哈希函数进行映射,映射到不同位置,发生哈希冲突的概率真的就极低了。但是这也造就了布隆过滤器的不准确特点:判断存在时不准确。

废话这么多,布隆过滤器就是个什么东西!

它的原理是用多个哈希函数,将数据映射到位图中;

这样既可以提高效率,又节省了空间。但是存有一个特点(缺点):判断一个东西只能说有可能存在,不能准确判断。

我们刚才提到了N个布隆过滤器的缺点,那么原因是什么呢?

我们接下来阐述: 

 布隆过滤器的缺点产生原因:

 布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,被映射的位置的比特位一定为1.

我们在查找的时候呢?

利用哈希函数计算位置,判断每个位置存储的值是否其为1,只要有一个0,我们就判断该元素不在哈希表中

如果对应位置全为1,就是在嘛,这个我们要三思了。看下面:

 倘若我们采用三个哈希函数,将元素“shusheng”映射到位图上1,4,7号位置,将元素“Qyuan”映射到位图上3,5,7号位置。

如果我要查找是否得到了offer ,输入了shusheng查找存在,好的往下输

输入了Qyuan查找存在,欣喜若狂

在输入offer,判断?我勒个去!我得到offer了吗? 

请嘴角上扬!

抱歉,没有!这只是一种巧合,offer映射的位置,由于被其他元素全都映射过,所以呢?判断它的时候,恰巧判断存在

 

 布隆过滤器的操作

 我们熟悉一个东西总是从增删查改开始!上面我们学习了增和查的思想!

改好像是完全没必要的,这个场景不需要!如果需要的话,我们可以和删一起思考一下,仅仅是增就可能发生误判现象!如果删和改,那么就不是不能准确判断一个元素存在的情况了,就连不存在都不能准确判断,布隆过滤器似乎没有价值了

这么半天,只是说了一个问题:布隆过滤器只支持增和查!

倘若支持删,那么每个位置只能有一个元素映射,代价将是巨大的

 

 代码实现:

#pragma once
#include<string>
#include"bitset.h"


struct HashStr1
{
	// BKDR
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= 131;
			hash += str[i];
		}

		return hash;
	}
};

struct HashStr2
{
	// RSHash
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		size_t magic = 63689; // 魔数
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= magic;
			hash += str[i];
			magic *= 378551;
		}

		return hash;
	}
};

struct HashStr3
{
	// SDBMHash
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		for (size_t i = 0; i < str.size(); ++i)
		{
			hash *= 65599;
			hash += str[i];
		}

		return hash;
	}
};


template<class K = std::string, class Hash1 = HashStr1, class Hash2 = HashStr2, class Hash3 = HashStr3>
class bloomfilter{
public:
	bloomfilter(const size_t n)
	:_bitset(5 * n)
	, _num(5* n)
	{}
	void set(const K& data)
	{
		Hash2 _hash;
		size_t index1 = Hash1()(data) % _num;
		size_t index2 = _hash(data) % _num;
		size_t index3 = Hash3()(data) % _num;
		_bitset.set(index1);
		_bitset.set(index2);
		_bitset.set(index3);
	}


	bool test(const K& data)
	{
		Hash2 _hash;
		size_t index1 = Hash1()(data) % _num;
		size_t index2 = _hash(data) % _num;
		size_t index3 = Hash3()(data) % _num;
		return _bitset.test(index1)&&_bitset.test(index2)&&_bitset.test(index3);

	}

private:
	BitSet::bitset _bitset;
	size_t _num;  //有效数据
};

 布隆过滤器的应用

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和 近似算法

近似算法呢?我们使用我们的布隆过滤器,将一个文件所有的query全部映射到我们的布隆过滤器上,在遍历另一个文件,判断该query是否在布隆过滤器上存在;

由于布隆过滤器可能将某个不在的query误判成存在!所以这种只能算是近似算法 。

 精确算法:因为100亿个query占用的内存过大,所以我们直接遍历两个文件对比!倘若我们将它们切割成我们能遍历的的大小,比如将一个文件切割成1000份乃至2000份,我们就可以将小文件的内容存入容器,查找交集。

但是问题又来了,我们怎么分割呢?

倘若随机分割,第一个文件分成1000份,每份文件为   

A1    A2   A3    ..............................................   A999

B1    B2   B3     ..............................................   B999(第二个文件)

我们就需要用A1和每一个B开头的文件进行查找交集的行为,A2,A3.....A999同样如此。只有这样,我们才能够精确地查找交集。

但是分割的办法变一下呢?我们利用哈希函数进行分隔!

相同的query一定进行相同序号的文件

那么我们只需要A1和B1 查交集  A2和B2 查交集。。。。。。。

这样我们的效率更高一些。

 


 给一个超过100G大小的log fifile, log中存着IP地址, 设计算法找到出现次数最多的IP地址?

 IP地址大部分用字符串表示,无论如何我们不可能直接遍历100G大小的文件。我们依旧利用哈希函数将大文件分隔成我们能遍历的小文件,每个小文件key-value模型的容器存储,我们就可以取得每个IP出现的次数,找到出现次数最多的哪一个也就不成什么问题了。

 要找出现次数前几个的IP地址

就是平常的topK问题,直接上堆。

 我们总结一下:

布隆过滤器只能判断元素在不在,并且有一定的误判,但是插入查找效率特别高,存储空间少

 

注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值