数据结构:布隆过滤器

布隆过滤器

假如现在有40亿个ip地址(string类型),然后给你一个ip地址,让你查找这个ip地址在不在这40亿个ip地址里?我们应该怎么做呢?

  1. 如果用哈希表来处理的话,这里有40亿的数据,数据量太大,因此太占用空间
  2. 如果用位图来处理的话,这里因为是字符串,有可能不同的字符串映射的是同一个位,会有哈希冲突的问题,导致误判
  3. 因此我们采用的是哈希+位图的方法,也就是布隆过滤器

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

布隆过滤器的优点:
  1. 查询效率高:使用了哈希的思想,一个数据映射了多个位
  2. 节省大量的内存空间:采用了位图
布隆过滤器的缺点:

布隆过滤器如果判断出该数据没有,那么这个数据一定没有,因为只要有一个映射的位不存在时,那么该数据就不存在。但是布隆过滤器如果判断出该数据有,那么其实是有可能出现误差的,因为字符串的计算难免会导致某两个字符串映射的几个位置都一样,因为我们写的哈希映射算法是固定的,字符串的随机排列组合是无限多的,所以如果两个字符串计算出来的映射的位一样时,就有可能导致误判。(也就是说这个字符串并不存在,但是由于映射的位和之前的字符串相同,所以就会判断出这个字符串也存在)

布隆过滤器的实现

在这里插入图片描述
布隆过滤器支持set接口,test接口,可以复用位图的set和test接口。但是布隆过滤器不能支持删除,因为通过多个哈希算法一个值会映射多个位,而且不同的值有可能会映射相同的位,如果删除的这个值所对应的位会变成0,那如果其他的值也映射这个位的话,就会导致其他的那个值被判定为不存在,但其实你并没有删除它。因此不支持删除。

那如果一定要实现删除操作呢?那就只能使用引用计数,但是采用引用计数删除的话就需要进行储存这个计数,需要额外的空间,会增大占用内存的大小。使用引用计数就是有多个值同时映射这个位,就把引用计数++,删除时就–,然后判断是否存在则是看值是否大于0。

具体代码如下:

BloomFilter.h
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include "bitset.h"
using namespace std;

template <class K, class HashFunc1, class HashFunc2, class HashFunc3>
class BloomFilter
{
public:
	BloomFilter(size_t keynum)//开多少位呢?
		:_bs(keynum * 5)
	{}
	void set(const K& key)
	{
		//如果是字符串,假如开了50个位,那么你映射的位不一定就在这50个位之间,因此要模上bitnum
		//使计算出来的位都在这50个位里进行映射
		size_t index1 = HashFunc1()(key) % _bs.GetBitNum();
		size_t index2 = HashFunc2()(key) % _bs.GetBitNum();
		size_t index3 = HashFunc3()(key) % _bs.GetBitNum();

		_bs.set(index1);
		_bs.set(index2);
		_bs.set(index3);
	}
	//void reset(const K& key)//布隆过滤器不支持reset操作,因为同一个位可能被不同的字符串所映射
	bool test(const K& key)
	{
		size_t index1 = HashFunc1()(key) % _bs.GetBitNum();
		if (_bs.test(index1) == false)
			return false;
		size_t index2 = HashFunc2()(key) % _bs.GetBitNum();
		if (_bs.test(index2) == false)
			return false;
		size_t index3 = HashFunc3()(key) % _bs.GetBitNum();
		if (_bs.test(index3) == false)
			return false;

		return true;//有可能会存在误判的情况
	}

private:
	bitset _bs;//位图
};

//字符串哈希算法
struct StrHash1
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};
struct StrHash2
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash *= 65599;
			hash += e;
		}
		return hash;
	}
};
struct StrHash3
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		size_t magic = 63689;
		for (auto e : s)
		{
			hash *= magic;
			hash += e;

			magic *= 378551;
		}
		return hash;
	}
};

void test_BloomFilter()
{
	BloomFilter<string, StrHash1, StrHash2, StrHash3> bf(10);
	cout << StrHash1()("https://mail.qq.com") << endl;
	cout << StrHash2()("https://mail.qq.com") << endl;
	cout << StrHash3()("https://mail.qq.com") << endl;

	string url1("https://mail.qq.com1");
	string url2("https://mail.qq.com2");
	string url3("https://mail.qq.com3");
	string url4("http://mail.qq.com");
	string url5("https://maill.qq.com");

	bf.set(url1);
	bf.set(url2);
	bf.set(url3);

	cout << bf.test(url1) << endl;
	cout << bf.test(url2) << endl;
	cout << bf.test(url3) << endl;
	cout << bf.test(url4) << endl;
	cout << bf.test(url5) << endl;
}
bitset.h
#pragma once
#include <iostream>
#include <vector>
using namespace std;

class bitset
{
public:
	bitset(size_t bitnum)//开多少位
		:_bitnum(bitnum)
	{
		_bit.resize((bitnum >> 3) + 1, 0);//除8+1是确定数组开多大,数组中的每一个元素都可以标识8个数据
	}
	void set(size_t x)
	{
		//size_t index = x / 8;//计算是数组的第几个段
		size_t index = x >> 3;
		size_t num = x % 8;//计算是这个段的第几个位

		//把对应的比特位置为1
		_bit[index] |= (1 << num);
	}
	void reset(size_t x)
	{
		size_t index = x >> 3;
		size_t num = x % 8;

		//把对应的比特位置为0
		_bit[index] &= (~(1 << num));
	}
	bool test(size_t x)
	{
		//判断对应位是0还是1
		size_t index = x >> 3;
		size_t num = x % 8;

		return _bit[index] & (1 << num);//与完之后的结果如果非零就说明存在,为零说明不存在
	}
	size_t GetBitNum()
	{
		return _bitnum;
	}
private:
	vector<char> _bit;
	size_t _bitnum;
};
test.cpp
#include "BloomFilter.h"
int main()
{
	test_BloomFilter();
	system("pause");
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章