namespace mystd
{
//叫bitset只是单纯和库里面统一而已,接口命名也是
//库里面bitset使用基本也是这几个接口
template<size_t N> //N表示要表示整数的范围,即比特位个数
class bitset
{
public:
bitset()
{
//初始化这里,可能存在浪费,但几个比特位而已
_bits.resize(N / 32 + 1, 0);
}
void set(size_t x) //设置标记,表示x存在
{
//给你x,计算出x属于第几个整数,整数中的第几个比特位
int i = x / 32; //第几个
int j = x % 32; //第几个比特位
//把对应的_bits[i]的第[j]位修改为1即可,方法是将1左移j位,或运算即可
_bits[i] |= (1 << j);
}
void reset(size_t x) //去标记,表示x不存在
{
int i = x / 32;
int j = x % 32;
//把对应的_bits[i]的第[j]位修改为0即可,方法(1)是将1左移j位 (2)取反(j位置为0,其它位置为1) (3)进行与运算
_bits[i] &= ~(1 << j);
}
bool test(size_t x) //看x在不在
{
int i = x / 32;
int j = x % 32;
//取到_bits[i]的第j位即可
return _bits[i] & (1 << j); //非0即真,0即假
}
private:
vector<int> _bits;
};
}
代码:
#include
#include
using namespace std;
//位图的应用
//快速查找某个数据是否在一个集合中
void test1()
{
vector<int> a = { 0, 1, 2, 3, 4, 8, 99, 100, 150 };
bitset<151> bit_set;
for (auto e : a)
{
bit_set.set(e);
}
int x = 0;
while (cin >> x)
{
if (bit_set.test(x))
{
cout << x << "存在" << endl;
}
else
{
cout << x << "不存在" << endl;
}
}
}
//排序 + 去重
void test2()
{
int a[] = {0, 1, 2, 3, 4, 8, 99, 99, 100, 100, 150};
bitset<151> bit_set;
vector<int> result;
for (auto e : a)
{
bit_set.set(e);
}
//实际中应该在建立位图的过程中找出最大最小值,这里就不写了
//min = 0, max = 150;
for (int i = 0; i <= 150; i++)
{
if (bit_set.test(i))
{
result.push_back(i);
}
}
for (auto e : result) cout << e << " ";
cout << endl;
}
//求两个集合的交集、并集等
void test3()
{
int a1[] = { 0, 1, 2, 3 };
int a2[] = { 0, 2, 2, 4, 5, 6 };
//交集
bitset<7> bit_set1;
bitset<7> bit_set2;
for (auto e : a1) bit_set1.set(e);
cout << "交集为:";
for (auto e : a2)
{
if (bit_set1.test(e) && bit_set2.test(e) == false)
{
bit_set2.set(e); //过滤掉多次出现的数据
cout << e << " ";
}
}
cout << endl;
//并集
cout << "并集为:";
bitset<7> bit_set3; //去掉a1中重复的部分
for (auto e : a1)
{
if (bit_set3.test(e) == false)
{
cout << e << " ";
bit_set3.set(e);
}
}
for (auto e : a2)
{
if (bit_set3.test(e) == false) //把a2特有的提取出来
{
cout << e << " ";
}
}
cout << endl;
}
想要判断某个数据在不在:
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
结合前面可知,布隆过滤器的查找需要对多个位置进行判断,都为1才认为存在,有一个为0认为不存在。
布隆过滤器存在误判,为什么还要使用?
以用户注册为例,用户名等信息存储在服务器数据库,每次注册新用户都要遍历所有数据,消耗太大。这个时候可以考虑使用布隆过滤器,对于判断不在的情况,是准确的,可以允许注册;对于判断在的情况就需要去遍历数据。实际当中可以过滤掉大量的请求,提高效率。
//三个字符串哈希函数
struct BKDRHash
{
size_t operator()(const string& key)
{
// BKDR
size_t hash = 0;
for (auto e : key)
{
hash *= 31;
hash += e;
}
return hash;
}
};
struct APHash
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (size_t i = 0; i < key.size(); i++)
{
char ch = key[i];
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& key)
{
size_t hash = 5381;
for (auto ch : key)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
//布隆过滤器一般都是解决字符串
//关于布隆过滤器的长度,len = N * x,一般x取三以上,至于具体大小依据场景进行衡量
//(1)x过大,空间浪费
//(2)x过小,误判较多(不在判断为在),过滤效果不好
template<size_t N, size_t x = 5, class K = string, class HashFun1 = BKDRHash, class HashFun2 = APHash, class HashFun3 = DJBHash>
class BloomFilter
{
void set(const K& key)
{
//一次标记3个位置,要让数值落在范围内部
size_t len = N * x;
size_t hash1 = HashFun1()(key) % len;
size_t hash2 = HashFun2()(key) % len;
size_t hash3 = HashFun3()(key) % len;
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
bool test(const K& key)
{
//看三个位置,有一个为0就返回false
size_t len = N * x;
size_t hash1 = HashFun1()(key) % len;
if (_bs.test(hash1) == false) return false;
size_t hash2 = HashFun2()(key) % len;
if (_bs.test(hash2) == false) return false;
size_t hash3 = HashFun3()(key) % len;
if (_bs.test(hash3) == false) return false;
return true;
}
private:
bitset<N * x> _bs;
};
布隆过滤器一般不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
比如:删除上图中"腾讯"元素,如果直接将该元素所对应的二进制比特位置0,“百度”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
(了解)一定要支持删除的话,可以采用引用计数的方法:
将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?