位图的应用场景引出:
位图概念:
用每一个比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常用于判断某个数据是否存在
位图的实现:
template<size_t N>
class bitset
{
public:
//构造函数
bitset()
{
//开多少char 一个char大小为一个字节=8个比特位
//为余的位数多开一个char
_bits.resize(N/8+1, 0);
}
//标记的时候是从低位往高位存
void set(size_t x)
{
//计算存在第几个char中
size_t i = x / 8;
//存在该char的第几个位里
size_t j = x % 8;
//当前char的每个位都是零,要将映射的位置置为1
// 原来的位置是1还是1 原来是0的位置还是零不变
//采用位运算的方法 高 00000000 低
// 00000001
//注意运算的优先级! 分不清就套括号
_bits[i] = _bits[i]|(1 << j);
}
//将元素的值映射的位置置为0
void reset(size_t x)
{
//计算存在第几个char中
size_t i = x / 8;
//存在该char的第几个位里
size_t j = x % 8;
//原来的0和1不变 所映射的位置置为0
// 00001010
// 00001000
// 11110111
//这里是按位取反 解决问题时可以运用逆向思维
_bits[i] = _bits[i]& (~(1 << j));
}
bool test(size_t x)
{
//计算存在第几个char中
size_t i = x / 8;
//存在该char的第几个位里
size_t j = x % 8;
//00000101
//00000100
return _bits[i] & (1 << j);
}
private:
//创建一个以char为存储元素的vector,一个char是8个bit位
vector<char> _bits;
};
测试用例:
void test_bit_set1()
{
bitset<100> bs1;
bs1.set(8);
bs1.set(9);
bs1.set(20);
cout << bs1.test(8) << endl;
cout << bs1.test(9) << endl;
cout << bs1.test(20) << endl;
bs1.reset(8);
bs1.reset(9);
bs1.reset(20);
cout << bs1.test(8) << endl;
cout << bs1.test(9) << endl;
cout << bs1.test(20) << endl;
}
运行结果及画图解释:
以上监视窗口是以16进制展示的结果
图示:
在位图上的扩展:
在此基础上,我们可以用两个位图来记录元素的数量,这样就可以筛选出出现过0 1 2 3次的数据个数
template<size_t N>
class twobitset
{
public:
//通过在两个位图相同的映射位置上置数来表示当前存储个数
void set(size_t x)
{
//高位
bool inset1 = _bs1.test(x);
//低位
bool inset2 = _bs2.test(x);
//插入之前要看该数据映射的位置原来的个数
// 00
if (inset1 == false && inset2 == false)
{
//原来是00 置为01
_bs2.set(x);
}
//01
else if (inset1 == false && inset2 == true)
{
_bs1.set(x);
_bs2.reset(x);
}
//10
else if (inset1 == true && inset2 == false)
{
_bs2.set(x);
}
//else if (inset1 == true && inset2 == false)
//{
// // ->11
// _bs1.set(x);
// _bs2.set(x);
//}
}
void print_once_num()
{
for (size_t i = 0;i < N;i++)
{
if (_bs1.test(i) == false && _bs2.test(i) == true)
{
cout << i << endl;
}
}
}
void print_twice_num()
{
for (size_t i = 0;i < N;i++)
{
if (_bs1.test(i) == true && _bs2.test(i) == false)
{
cout << i << endl;
}
}
}
void print_third_num()
{
for (size_t i = 0;i < N;i++)
{
if (_bs1.test(i) == true && _bs2.test(i) == true)
{
cout << i << endl;
}
}
}
private:
bitset<N> _bs1;
bitset<N> _bs2;
};
布隆过滤器
即用多个哈希函数,将一个数据映射到多个哈希地址中,可以用来告诉你“某个数据一定不存在或者可能存在”,既提升了查询效率,也可以节省大量的空间。
布隆过滤器的实现:
struct HashBKDR
{
// BKDR
size_t operator()(const string& key)
{
size_t val = 0;
for (auto ch : key)
{
val *= 131;
val += ch;
}
return val;
}
};
struct HashAP
{
// BKDR
size_t operator()(const string& key)
{
size_t hash = 0;
for (size_t i = 0; i < key.size(); i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
}
}
return hash;
}
};
struct HashDJB
{
// BKDR
size_t operator()(const string& key)
{
size_t hash = 5381;
for (auto ch : key)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
//N表示要映射N个值
template<size_t N,
class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
class BloomFilter
{
public:
void Set(const K& key)
{
// 要使用类的重载函数要先建立一个类的对象
// 这里使用的是类的匿名对象
size_t hash1 = Hash1()(key) % (_ratio * N);
//cout << hash1 << endl;
_bits->set(hash1);
size_t hash2 = Hash2()(key) % (_ratio * N);
//cout << hash2 << endl;
_bits->set(hash2);
size_t hash3 = Hash3()(key) % (_ratio * N);
//cout << hash3 << endl;
_bits->set(hash3);
}
bool Test(const K& key)
{
//三个位置只要有一个不在就肯定不在
size_t hash1 = Hash1()(key) % (_ratio * N);
//cout << hash1 << endl;
if (_bits->test(hash1)==false)
{
return false;
}
size_t hash2 = Hash2()(key) % (_ratio * N);
//cout << hash2 << endl;
if (_bits->test(hash2) == false)
{
return false;
}
size_t hash3 = Hash3()(key) % (_ratio * N);
//cout << hash3 << endl;
if (_bits->test(hash3)==false)
{
return false;
}
return true;
}
private:
const static size_t _ratio = 5;
//根据公式得出,开出的位数=4.2*N 这里把4.2给成5 即一个数据开5个位
//这样开空间可以使误判率降低
std::bitset<_ratio* N>* _bits = new std::bitset<_ratio* N>;
};