【資料結構】點陣圖BitMap與布隆過濾器BloomFilter
首先先看一下下面這個騰訊的面試題:
給40億個不重複的無符號整數,沒排過序。給一個無符號整數,如何快速判斷一個數是否在這40億個數中。 【騰訊】
思路一:
最容易想到的解法就是遍歷所有的40多億個整數,然後一個一個判斷。但是這個需要花費的記憶體是多大呢?
大家可以去算一下,這裡我就直接給出結果為16G,是不是需要的空間很大啊。如果面試官給出限制條件,要你使用的空間少於多少,遍歷的方法就行不通啦。
思路二:
我們可以把一個整形再細分一下,一個int型別就可以程式設計32個位,每一位用0,1表示當前這個位置上是否存有值,同樣是利用雜湊儲存的方法。只是這樣儲存的話就可以減少很多的空間了,例如上題使用的記憶體就可以從16G降到500M的記憶體。空間的使用率減少了不止一點。
點陣圖的實現
#ifndef __BITMAP_H__ #define __BITMAP_H__ #include <vector> #include "Common.h" class BitMap { public: BitMap(size_t size = 0) :_size(0) { _a.resize((size>>5)+1); } //插入資料 void Set(size_t x) { size_t index = x >> 5; size_t num = x % 32; //如果當前位置不存在值,直接插入 if (!(_a[index] & (1 << num)))//判斷這個位上是不是等0的 { ++_size; _a[index] |= (1 << num);//將當前位上的值置成1 } } void Reset(size_t x) { size_t index = x >> 5; size_t num = x % 32; //判斷當前位上的值是不是等於1,等於1刪除 if (_a[index] & (1 << num)) { --_size; _a[index] &= ~(1 << num);//將當前位置成0 } } bool Test(size_t x) { size_t index = x >> 5; size_t num = x % 32; //如果當前位等於1,那麼存在 if (_a[index] & (1 << num)) { return true; } return false; } void Resize(size_t size) { _a.resize((size >> 5) + 1); } size_t Size() { return _size; } private: vector<size_t> _a; size_t _size;//點陣圖上插入了多少值 }; void Test() { BitMap bm(35); bm.Set(4); bm.Set(5); bm.Set(6); cout << "is4Exist?->" << bm.Test(4) << endl; cout <<"is5Exist?->"<< bm.Test(5) << endl; bm.Reset(5); cout << "is4Exist?->" << bm.Test(4) << endl; cout << "is5Exist?->" << bm.Test(5) << endl; } #endif //__BITMAP_H__
布隆過濾器 Bloom Filter
原理
如果想判斷一個元素是不是在一個集合裡,一般想到的是將集合中所有元素儲存起來,然後通過比較確定。連結串列、樹、散列表(又叫雜湊表,Hash table)等等資料結構都是這種思路。但是隨著集合中元素的增加,我們需要的儲存空間越來越大。同時檢索速度也越來越慢。
Bloom Filter 是一種空間效率很高的隨機資料結構,Bloom filter 可以看做是對 bit-map 的擴充套件, 它的原理是:
當一個元素被加入集合時,通過 K
個 Hash函式
將這個元素對映成一個位陣列(Bit array)中的 K 個點
,把它們置為 1
。檢索時,我們只要看看這些點是不是都是 1 就(大約)知道集合中有沒有它了:
如果這些點有任何一個 0,則被檢索元素一定不在;
如果都是 1,則被檢索元素可能在。
如果只是空洞的說這些原理的話,肯定大家都不知道布隆過濾器有什麼用處。布隆過濾器對於單機來說可能用處不是很大,但對於分散式來說就比較有用了。
如主從分佈:一個數組過來,我想要知道他是不是在記憶體中,我們是不是需要一個一個去訪問磁碟,判斷資料是否存在。但是問題來了訪問磁碟的速度是很慢的,所以效率會很低,如果使用布隆過濾器,我們就可以先去過濾器這個集合裡面找一下對應的位置的資料是否存在。雖然布隆過濾器有他的缺陷,但是我們能夠知道的是當前位置為0是肯定不存在的,如果都不存在,就不需要去訪問了。
下面來講一下布隆過濾器的缺陷:
缺陷一:誤算率(False Positive)是其中之一。隨著存入的元素數量增加,誤算率隨之增加。但是如果元素數量太少,則使用散列表足矣。所以我們用多個雜湊表去 儲存一個數據。那麼問題又來了,我們是多用一些呢,還是少用一些。如果多用雜湊表的話,如上面的題,一個雜湊就需要500M,那麼放的越多是不是越佔記憶體啊。如果太少的話是不是誤算率就高啊,所以取個適中的。下面我的實現是取了五個雜湊表(沒有什麼根據,只是把思路展現出來一下,能夠分析出取多少個,那都是大牛們弄出來的演算法,我當前水平不夠~)
缺陷二:如果當前位置為0肯定不存在,但是為1不一定存在
布隆過濾器的實現:(用了素數表和5個雜湊演算法)
一、5個雜湊演算法的實現
#ifndef __COMMON_H__
#define __COMMON_H__
size_t GetPrimeSize(size_t size)
{
static const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
for (size_t i = 0; i < _PrimeSize; i++)
{
if (_PrimeList[i] > size)
{
return _PrimeList[i];
}
if (_PrimeList[_PrimeSize] == size)
return _PrimeList[_PrimeSize - 1];
}
return _PrimeList[_PrimeSize - 1];
}
template<class T>
struct __HashFunc1
{
size_t BKDRHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch; // 也可以乘以31、131、1313、13131、131313..
}
return hash;
}
size_t operator()(const T& str)
{
return BKDRHash(str.c_str());
}
};
template<class T>
struct __HashFunc2
{
size_t SDBMHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
size_t operator()(const T& str)
{
return SDBMHash(str.c_str());
}
};
template<class T>
struct __HashFunc3
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
size_t operator()(const T& str)
{
return RSHash(str.c_str());
}
};
template<class T>
struct __HashFunc4
{
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;
}
size_t operator()(const T& str)
{
return APHash(str.c_str());
}
};
template<class T>
struct __HashFunc5
{
size_t JSHash(const char *str)
{
if (!*str) // 這是由本人新增,以保證空字串返回雜湊值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const T& str)
{
return JSHash(str.c_str());
}
};
#endif //__COMMON_H__
二、布隆過濾器
#ifndef __BLOOMFILTER_H__
#define __BLOOMFILTER_H__
#include <iostream>
using namespace std;
#include<string>
#include"BitMap.h"
#include "Common.h"
template<class K = string,
class HashFunc1 = __HashFunc1<K>,
class HashFunc2 = __HashFunc2<K>,
class HashFunc3 = __HashFunc3<K>,
class HashFunc4 = __HashFunc4<K>,
class HashFunc5 = __HashFunc5<K>>
class BloomFilter
{
public:
BloomFilter(size_t size = 0)
{
_capacity = GetPrimeSize(size);
_bitMap.Resize(_capacity);
}
void Set(const K& key)
{
size_t index1 = HashFunc1()(key);
size_t index2 = HashFunc2()(key);
size_t index3 = HashFunc3()(key);
size_t index4 = HashFunc4()(key);
size_t index5 = HashFunc5()(key);
_bitMap.Set(index1%_capacity);//設定為第多少位的數,然後呼叫點陣圖的Set設定成第幾個位元組的第幾位
_bitMap.Set(index2%_capacity);
_bitMap.Set(index3%_capacity);
_bitMap.Set(index4%_capacity);
_bitMap.Set(index5%_capacity);
}
bool Test(const K& key)
{
size_t index1 = HashFunc1()(key);
if (!(_bitMap.Test(index1%_capacity)))//為1不一定存在,為0肯定不存在
return false;
size_t index2 = HashFunc2()(key);
if (!(_bitMap.Test(index2%_capacity)))
return false;
size_t index3 = HashFunc3()(key);
if (!(_bitMap.Test(index3%_capacity)))
return false;
size_t index4 = HashFunc4()(key);
if (!(_bitMap.Test(index4%_capacity)))
return false;
size_t index5 = HashFunc4()(key);
if (!(_bitMap.Test(index5%_capacity)))
return false;
return true;
}
protected:
BitMap _bitMap;
size_t _capacity;
};
void TestBloomFilter()
{
BloomFilter<> bf(50);
bf.Set("臧");
bf.Set("靜");
bf.Set("位元");
bf.Set("peter");
bf.Set("徐");
bf.Set("http://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html");
bf.Set("http://www.cnblogs.com/-clq/archive/2012/05/31/2528155.html");
cout << "Exist?->:" << bf.Test("臧") << endl;
cout << "Exist?->:" << bf.Test("靜") << endl;
cout << "Exist?->:" << bf.Test("peter") << endl;
cout << "Exist?->:" << bf.Test("徐航") << endl;
cout << "Exist?->:" << bf.Test("http://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html") << endl;
cout << "Exist?->:" << bf.Test("http://www.cnblogs.com/-clq/archive/2012/05/31/25288153.html") << endl;
}
#endif //__BLOOMFILTER_H__