C++布隆過濾器

布隆過濾器

這名詞有沒有聽着好像很 挺高大上的,的確,它也是一種很重要的結構,下面一起看看:

一:說說歷史:

(Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。它實際上是由一個很長的二進制向量一系列隨機映射函數組成,布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法缺點是有一定的誤識別率(假正例False positives,即Bloom Filter報告某一元素存在於某集合中,但是實際上該元素並不在集合中)和刪除困難,但是沒有識別錯誤的情形(即假反例False negatives,如果某個元素確實沒有在該集合中,那麼Bloom Filter 是不會報告該元素存在於集合中的,所以不會漏報)。

在日常生活中,包括在設計計算機軟件時,我們經常要判斷一個元素是否在一個集合中。比如在字處理軟件中,需要檢查一個英語單詞是否拼寫正確(也就是要判斷 它是否在已知的字典中);

在 FBI,一個嫌疑人的名字是否已經在嫌疑名單上;在網絡爬蟲裏,一個網址是否被訪問過等等。最直接的方法就是將集合中全部的元素存在計算機中,遇到一個新 元素時,將它和集合中的元素直接比較即可。一般來講,計算機中的集合是用哈希表(hash table)來存儲的。它的好處是快速準確,缺點是費存儲空間。當集合比較小時,這個問題不顯著,但是當集合巨大時,哈希表存儲效率低的問題就顯現出來 了

比如說,一個象 Yahoo,Hotmail 和 Gmai 那樣的公衆電子郵件(email)提供商,總是需要過濾來自發送垃圾郵件的人(spamer)的垃圾郵件。一個辦法就是記錄下那些發垃圾郵件的 email 地址。由於那些發送者不停地在註冊新的地址,全世界少說也有幾十億個發垃圾郵件的地址,將他們都存起來則需要大量的網絡服務器。如果用哈希表,每存儲一億 個 email 地址, 就需要 1.6GB 的內存(用哈希表實現的具體辦法是將每一個 email 地址對應成一個八字節的信息指紋,然後將這些信息指紋存入哈希表,由於哈希表的存儲效率一般只有 50%,因此一個 email 地址需要佔用十六個字節。一億個地址大約要 1.6GB, 即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百 GB 的內存。除非是超級計算機,一般服務器是無法存儲的[2]。(該段引用谷歌數學之美:http://www.google.com.hk/ggblog/googlechinablog/2007/07/bloom-filter_7469.html)

二:概念:

如果想判斷一個元素是不是在一個集合裏,一般想到的是將所有元素保存起來,然後通過比較確定。鏈表,樹等等數據結構都是這種思路.但是隨着集合中元素的增加,我們需要的存儲空間越來越大,檢索速度也越來越慢。不過世界上還有一種叫作散列表(又叫哈希表,Hash table)的數據結構。它可以通過一個Hash函數將一個元素映射成一個位陣列(Bit Array)中的一個點(關於位陣列,即數據結構位圖,詳見位圖見我另一篇博客:位圖BitMap。這樣一來,我們只要看看這個點是不是 1就知道可以集合中有沒有它了。這就是布隆過濾器的基本思想。

Hash面臨的問題就是衝突。假設 Hash 函數是良好的,如果我們的位陣列長度爲 m 個點,那麼如果我們想將衝突率降低到例如 1%, 這個散列表就只能容納 m/100 個元素。顯然這就不叫空間有效了(Space-efficient)。解決方法也簡單,就是使用多個 Hash(如下圖所示),如果它們有一個說元素不在集合中,那肯定就不在。如果它們都說在,雖然也有一定可能性它們在說謊,不過直覺上判斷這種事情的概率是比較低的。

代碼:

bitmap.h

#ifndef _BIT_MAP_H
#define _BIT_MAP_H

#include<iostream>
#include<vector>
using namespace std;

/*
*一個數據32位,40億個整數,每個整數需用一位表示,40億位就完事
*/

class BitMap
{
public:
	BitMap()
		:_size(0)
	{}

	BitMap(size_t size)
		:_size(0)
	{
		_array.resize((size>>5)+1);  //多少個數據,一個數據佔32位,加一是至少一個數據
	}

	bool Set(size_t num)
	{
		size_t index = num >> 5;     //計算在哪個數據上
		size_t n = num % 32;

		if (_array[index] & (1 << (31 - n)))   //移位問題
		{
			cout << "有數據" << endl;
			return false;
		}
		else
		{
			size_t a = 1 << (31 - n);
			_array[index] |= a;
			++_size;
			return true;
		}
	}

	bool ReSet(size_t num)   //刪除一個數 之後重置
	{
		size_t index = num >> 5;
		size_t n = num % 32;

		if (_array[index] & (1 << (31 - n)))   //數存在 刪除
		{
			_array[index] &= (~(1 << (31 - n)));
			--_size;
			return true;
		}
		else
		{
			return false;  //不存在這個數
		}
	}

private:
	vector<size_t> _array; //數組
	size_t _size;          //位圖中數據個數
};

#endif


void Test()
{
	BitMap bm(65);

	for (int i = 0; i < 32; ++i)
	{
		bm.Set(i);
	}

	bm.ReSet(0);
}
HashFun.h
#pragma once
template<class T>  //各類哈希函數
size_t BKDRHash(const char *str)
{
	register size_t hash = 0;
	while (size_t ch = (size_t)*str++)
	{
		hash = hash * 131 + ch;
	}
	return hash;
}

template<class T>
size_t SDBMHash(const char *str)
{
	register size_t hash = 0;
	while (size_t ch = (size_t)*str++)
	{
		hash = 65599 * hash + ch; 
	}
	return hash;
}

template<class T>
size_t RSHash(const char * str)
{
	size_t hash = 0;
	size_t magic = 63689;
	while (size_t ch = (size_t)*str++)
	{
		hash = hash * magic + ch;
		magic *= 378551;
	}
	return hash;
}


template<class T>
size_t APHash(const char *str)
{
	register size_t hash = 0;
	size_t ch;
	for (long i = 0; ch = (size_t)*str++; i++)
	{
		if ((i & 1) == 0)
		{
			hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
		}
		else
		{
			hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
		}
	}
	return hash;
}


template<class T>
size_t JSHash(const char* str)
{
	if (!*str)
	{
		return 0;
	} 
	size_t hash = 1315423911;
	while (size_t ch = (size_t)*str++)
	{
		hash ^= ((hash << 5) + ch + (hash >> 2));
	}
	return hash;
}
Bloom_Filter.h
#pragma once

#include"BitMap.h"
#include"HashFun.h"

template<class T>
struct __HashFun1          //5種哈希函數對應的仿函數
{
	size_t operator()(const T& key)
	{
		return BKDRHash<T>(key.c_str());
	}
};

template<class T>
struct __HashFun2
{
	size_t operator()(const T& key)
	{
		return SDBMHash<T>(key.c_str());
	}
};

template<class T>
struct __HashFun3
{
	size_t operator()(const T& key)
	{
		return RSHash<T>(key.c_str());
	}
};

template<class T>
struct __HashFun4
{
	size_t operator()(const T& key)
	{
		return APHash<T>(key.c_str());
	}
};

template<class T>
struct __HashFun5
{
	size_t operator()(const T& key)
	{
		return JSHash<T>(key.c_str());
	}
};


template<class K = string,
class HashFun1 = __HashFun1<K>,
class HashFun2 = __HashFun2<K>,
class HashFun3 = __HashFun3<K>,
class HashFun4 = __HashFun4<K>,
class HashFun5 = __HashFun5<K>>
class Bloom_Filter
{
public:
	Bloom_Filter(size_t size)
		:_capacity(size)
	{
		_bitmap._array.resize((size >> 5) + 1);
	}

	void _Set(const K& key)
	{
		_bitmap.Set(HashFun1()(key) % _capacity);
		_bitmap.Set(HashFun2()(key) % _capacity);
		_bitmap.Set(HashFun3()(key) % _capacity);
		_bitmap.Set(HashFun4()(key) % _capacity);
		_bitmap.Set(HashFun5()(key) % _capacity);
	}

	bool _IsIn(const K& key)
	{
		if (!_bitmap.Test(HashFun1()(key) % _capacity))
			return false;
		if (!_bitmap.Test(HashFun1()(key) % _capacity))
			return false;
		if (!_bitmap.Test(HashFun1()(key) % _capacity))
			return false;
		if (!_bitmap.Test(HashFun1()(key) % _capacity))
			return false;
		if (!_bitmap.Test(HashFun1()(key) % _capacity))
			return false;
		return true;
	}
private:
	BitMap _bitmap;
	size_t _capacity;
};

三、布隆過濾器優缺點:

1.優點:

相比於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優勢。布隆過濾器存儲空間和插入/查詢時間都是常數。另外, Hash 函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優勢。布隆過濾器可以表示全集,其它任何數據結構都不能;k 和 m 相同,使用同一組 Hash 函數的兩個布隆過濾器的交併差運算可以使用位操作進行。

  2.缺點

但是布隆過濾器的缺點和優點一樣明顯。誤算率(False Positive)是其中之一。隨着存入的元素數量增加,誤算率隨之增加。但是如果元素數量太少,則使用散列表足矣。另外,一般情況下不能從布隆過濾器中刪除元素. 我們很容易想到把位列陣變成整數數組,每插入一個元素相應的計數器加1, 這樣刪除元素時將計數器減掉就可以了。然而要保證安全的刪除元素並非如此簡單。首先我們必須保證刪除的元素的確在布隆過濾器裏面. 這一點單憑這個過濾器是無法保證的。另外計數器迴繞也會造成問題。

賜教!

發佈了109 篇原創文章 · 獲贊 424 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章