數據結構:布隆過濾器

布隆過濾器

假如現在有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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章