所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景,通常是用来判断某个数据是否存在
比方说现在有40亿个未排序的无符号整数,请问如何判断一个数是否存在于这40亿个数中?
这个问题就很适合用位图来解决,题目中说这些数据都是无符号整数而无符号整数最大能取到0xffffffff(约43亿),并且题目只要求我们判断该数据是否存在,因此我们只需要用一个比特位来标识该数值是否存在即可
如上图,插入了1, 3, 7三个数据,我们只要把第1, 3, 7个比特位赋值1即可。
namespace ssj
{
template<size_t N>
class bitset
{
public:
bitset()
{
// 初始化,因为/向下取整,因此我们分配一个字节即可
_bits.resize(N / 8 + 1, 0);
}
void set(size_t x) // 标记该数存在
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= (1 << j);
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= (~(1 << j));
}
bool test(size_t x) // 检查该数是否存在
{
size_t t = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);
}
private:
std::vector<char> _bits;
};
}
template<size_t N>
class TwoBitSet
{
public:
void Set(size_t x)
{
if (!bs1.test(x) && !_bs2.test(x))
{
_bs2.set(x);
}
else if (!_bs1.test(x) && _bs2.test(x))
{
_bs1.set(x);
_bs2.reset(x);
// 10 表示已经出现2次或以上,不用处理
}
}
void PrintOnceNum()
{
for (size_t i = 0; i < N; ++i)
{
if (!_bs1.teset(i) && _bs2.test(i))
{
cout << i << endl;
}
}
}
private:
ssj::bitset<N> _bs1;
ssj::bitset<N> _bs2;
};
位图固然是一种非常优秀的结构,他不仅节省空间而且效率高,但他也有一个很大的局限性——只能处理整数。对字符串和自定义类型对象位图没办法处理。原因很直观,对整型来讲,不管是长整型还是无符号整型数据,他们的数量终究是有限并且可以很简单的和某个位置一一对应,但是字符串的非常数量非常多,其中字符的增多和减少,或者位置的互换都会形成新的字符串,因此在有限的比特位中若我们想给每个字符串都提供一个比特位,那么必然会造成位置冲突的局面。现在要介绍的布隆过滤器可以比较有效的解决这个问题。
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑型的,比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或可能存在”,它是用多个哈希函数,将一个数据映射到 位图结构中,此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
#include
#include
#include
using namespace std;
/*下面三个都是哈希函数,可以自行选择合
适的哈希函数,数量最好控制在3-5个*/
struct BKDRHash
{
size_t operator()(const string& s)
{
// BKDR
size_t value = 0;
for (auto ch : s)
{
value *= 31;
value += ch;
}
return value;
}
};
struct APHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (long i = 0; i < s.size(); i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
// X代表我们所给的比特位是数据量的几倍
template <size_t N,
size_t X = 8,
class K = string,
class HashFunc1 = BKDRHash,
class HashFunc2 = APHash,
class HashFunc3 = DJBHash>
class BloomFilter
{
public:
void Set(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
size_t index2 = HashFunc2()(key) % len;
size_t index3 = HashFunc3()(key) % len;
_bs.set(index1);
_bs.set(index2);
_bs.set(index3);
}
// 检查该数据是否存在
bool Test(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
if (_bs.test(index1) == false) return false;
size_t index2 = HashFunc2()(key) % len;
if (_bs.test(index2) == false) return false;
size_t index3 = HashFunc3()(key) % len;
if (_bs.test1(index3) == false) return false;
return true;
}
private:
bitset<X* N> _bs;
};
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
一种删除方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给计数器加一,删除时减一,通过多占用几倍存储空间的代价来增加删除操作。