位图
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用 来判断某个数据存不存在的。
位图的应用
1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记
位图的实现
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace szg
{
template<size_t N>
class bitset
{
public:
bitset()
{
int n = N / 8 + 1;
_bitmap.resize(n);
}
void set(size_t x)
{
int n = x / 8;
int m = x % 8;
_bitmap[n] |= 1 << m;
}
void reset(size_t x)
{
int n = x / 8;
int m = x % 8;
_bitmap[n] &= ~(1 << m);
}
bool test(size_t x)
{
int n = x / 8;
int m = x % 8;
return _bitmap[n] & (1 << m);
}
private:
vector<char> _bitmap;
};
void test_bitset()
{
//bitset<100> bs1;
//bitset<-1> bs2;
bitset<0xffffffff> bs2;
bs2.set(10);
bs2.set(10000);
bs2.set(8888);
cout << bs2.test(10) << endl;
cout << bs2.test(10000) << endl;
cout << bs2.test(8888) << endl;
cout << bs2.test(8887) << endl;
cout << bs2.test(9999) << endl << endl;
bs2.reset(8888);
bs2.set(8887);
cout << bs2.test(10) << endl;
cout << bs2.test(10000) << endl;
cout << bs2.test(8888) << endl;
cout << bs2.test(8887) << endl;
cout << bs2.test(9999) << endl;
}
}
注意:位图的缺点时只能针对整型,且范围要集中。
布隆过滤器
讲述布隆过滤器的原理之前,我们先思考一下,通常你判断某个元素是否存在用的是什么?应该蛮多人回答 位图吧,确实可以将值映射到 位图的 Key,然后可以在 O(1) 的时间复杂度内返回结果,,效率奇高。但是 位图 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,而一旦你的值很多例如上亿的时候,那 位图占据的内存大小就变得很可观了。
并且以string类型经过哈希转换后映射到位图上会出现误判,会出现多个元素映射到同一个位置的情况。
为了解决位图的缺点,我们引入了布隆过滤器。(布隆过滤器只能降低误判率,不能完全消除误判)
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概 率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存 在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也 可以节省大量的内存空间。
从图中可以看出,布隆过滤器一个元素映射多个位置(使用多个哈希函数),判断结果不在是准确的,判断结果存在时是不准确的。
k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数
布隆过滤器实现
#pragma once
#include<bitset>
#include<iostream>
#include<string>
#include<vector>
using namespace std;
namespace szg
{
struct BKDRHash
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto ch : key)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
struct APHash
{
size_t operator()(const string& key)
{
unsigned int hash = 0;
int i = 0;
for (auto ch : key)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
}
++i;
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& key)
{
unsigned int hash = 5381;
for (auto ch : key)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
struct JSHash
{
size_t operator()(const string& s)
{
size_t hash = 1315423911;
for (auto ch : s)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
};
template<size_t N,size_t x = 5, class K = string, class hash1 = BKDRHash, class hash2 = APHash, class hash3 = DJBHash>
class BloomFilter
{
public:
//插入数据(设置映射)
void set(const K& k)
{
size_t hashi1 = hash1()(k) % (N * x);
size_t hashi2 = hash2()(k) % (N * x);
size_t hashi3 = hash3()(k) % (N * x);
_bs.set(hashi1);
_bs.set(hashi2);
_bs.set(hashi3);
}
bool test(const K& k)
{
size_t hashi1 = hash1()(k) % (N * x);
size_t hashi2 = hash2()(k) % (N * x);
size_t hashi3 = hash3()(k) % (N * x);
if (_bs.test(hashi1) && _bs.test(hashi2) && _bs.test(hashi3))//可能存在
{
return true;
}
else//一定不存在
{
return false;
}
}
private:
bitset<N * x> _bs;
};
void test_bloomfilter1()
{
string str[] = { "猪八戒", "孙悟空", "沙悟净", "唐三藏", "白龙马1","1白龙马","白1龙马","白11龙马","1白龙马1" };
BloomFilter<10> bf;
for (auto& str : str)
{
bf.set(str);
}
for (auto& s : str)
{
cout << bf.test(s) << endl;
}
cout << endl;
srand(time(0));
for (const auto& s : str)
{
cout << bf.test(s + to_string(rand())) << endl;
}
}
void test_bloomfilter2()
{
srand(time(0));
const size_t N = 100000;
BloomFilter<N, 8> bf;
std::vector<std::string> v1;
std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
for (size_t i = 0; i < N; ++i)
{
v1.push_back(url + std::to_string(i));
}
for (auto& str : v1)
{
bf.set(str);
}
// v2跟v1是相似字符串集,但是不一样
std::vector<std::string> v2;
for (size_t i = 0; i < N; ++i)
{
std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
url += std::to_string(999999 + i);
v2.push_back(url);
}
size_t n2 = 0;
for (auto& str : v2)
{
if (bf.test(str))
{
++n2;
}
}
cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;
// 不相似字符串集
std::vector<std::string> v3;
for (size_t i = 0; i < N; ++i)
{
string url = "zhihu.com";
url += std::to_string(i + rand());
v3.push_back(url);
}
size_t n3 = 0;
for (auto& str : v3)
{
if (bf.test(str))
{
++n3;
}
}
cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}
}
1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无 关
2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算