布隆过滤器
位图
位图的概念
在介绍位图之前我们先看一个题目
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。
位图的实现
首先我们要解决如何开辟和映射的问题——==因为C++是不支持按bit位开辟空间的!
==也可以按照int类型 除8与模8改成除32与模32==
成员变量
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
template<size_t N>//使用非类型模版参数
class bitset
{
private:
std::vector<char> _bit;
};
}
成员函数
构造函数
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
template<size_t N>//使用非类型模版参数
class bitset
{
public:
bitset()
{
//我们开辟的空间不是N,而是N/8+1
//因为我们要的是N个bit,而一个字节是8个bit
//为什么要+1呢因为防止不是8的倍数,例如20,20/8=2,但是20个bit需要3个字节
//所以我们要+1
_bit.resize(N/8+1,0);
//如果想使用位移要记得加括号,因为<<的优先级比+低
//_bit.resize((N>>3)+1,0);
}
private:
std::vector<char> _bit;
};
}
set
set就是将bit位置为1
#include <vector>
namespace MySTL
{
template<size_t N>//使用非类型模版参数
class bitset
{
public:
void set(size_t x)//set就是将bit位置为1
{
size_t index = x/8;//确定在哪个字节
size_t pos = x % 8;//确定在字节中的哪个bit
//将对应的BIT位置为1
//可以使用或进行运算!
//因为或要两个都为0才为0,其他情况都为1
//我们只要有一个除了与这个BIT对应的位置为1,其他位置都为0的数
//我们就可以通过或将这个BIT位置为1,其他位置不变
//无论这个BIT原来是0还是1,都会变成1
_bit[index] |= (1<<pos);
//(1<<pos),将1左移pos位,就是将1放在pos位置上
//因为二级制位是左高右低,所以要向左移动pos位
//这里和大小端没有关系,因为我们使用的是char,char是一个字节
}
private:
std::vector<char> _bit;
};
}
reset
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
template<size_t N>//使用非类型模版参数
class bitset
{
public:
void reset(size_t x)//reset就是将bit位置为0
{
size_t index = x/8;
size_t pos = x % 8;
//reset是将该bit位置为0.无论原先是0还是1
//这时候我们可以使用与运算
//因为与运算要两个都为1才为1,其他情况都为0
//我们可以使用一个除了这个BIT位置为0,其他位置都为1的数
//例如pos的值为 2,先将1左移2位,就是将1放在第二位上
//然后进行取反,就是将这个数的所有位取反
//然后再进行与运算,就可以将这个BIT位置为0,其他位置不变
_bit[index] &= ~(1<<pos);
}
private:
std::vector<char> _bit;
};
}
test
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
template<size_t N>//使用非类型模版参数
class bitset
{
public:
bool test(size_t x)//检查这个bit是否为1
{
//这个也很简单,我们只要有一个除了这个BIT位置为1,其他位置都为0的数
//然后进行与运算,就可以得到这个BIT的值
//如果为0,就是false,如果不为,就是true
size_t index = x/8;
size_t pos = x % 8;
return _bit[index] & (1<<pos);
}
private:
std::vector<char> _bit;
};
}
位图总结
==位图用来判断大量的整形数据存不存在的时候效率是非常高的!==
==但是位图不是万能的!因为位图只能用来判断整形!==
而库里面也为我们提供了这个库
==不过库里面的接口比我们实现的更多,我们主要实现了的它的几个关节接口的实现!==
位图应用
==位图一般都是用来处理大量的数据==
例1:给定100亿个整数,设计算法找到只出现一次的整数?
位图优缺点
位图的优点
- 节省空间
- 效率高(快)
位图的缺点
- 要求范围相对集中,范围特别分散,空间消耗就提升(例如有100个值,一个最小是1,最大是42亿,那么消耗就很大了!)
- 只能针对整形!
布隆过滤器的概念
为什么会有布隆过滤器这种东西呢?
==布隆过滤器其实是在位图的基础上进一步的产物==
从位图我们可以看出来,判断一个值是否存在其实不用那么麻烦,如果是使用哈希表或者红黑树,我们还要把值都存起来!然后再找!这样子其实消耗很大!如果数据量太大了!那么红黑树和哈希表就不够用了!——所以有了位图,==只用一个标记位来标记一个值在或者不再!==
==上面我们知道位图的缺点之一就是只能针对整形!那么我们有没有办法字符串进行映射呢?——答案是使用哈希函数(hashFunc(str))将字符串转换整形!然后通过这个整形来映射!不止是字符串!我们可以通过不同的哈希函数!将不同的类型转换为整形!==
==布隆过滤器就是上面方法的一种优化!==
既然会出现误判!那么我们有什么办法来减少误判呢?
==但是这种方法是不能避免误判的!只能减小误判的概率!因为有可能所有的位置都跟别的冲突了!==——这种方法就叫布隆过滤器!
==布隆过滤器的改进==
映射多个位置,降低误判率!(但是不是映射位置越多越好!应该这样子会造成很大空间浪费!)
布隆过滤器的应用
1 )可以适用于一些不一定准确的场景——注册时候的昵称判重!
布隆过滤器的实现
#pragma once
#include <iostream>
#include <bitset>
#include <string>
using namespace std;
namespace MySTL
{
template<class T>
struct BKDRHash
{
size_t operator()(const T &str)
{
size_t hash = 0;
for(auto chi :str)
{
size_t ch = (size_t)chi;
hash = hash * 131 + ch;
}
return hash;
}
};
template<class T>
struct SDBMHash
{
size_t operator()(const T &str)
{
size_t hash = 0;
for(auto chi :str)
{
size_t ch = (size_t)chi;
hash = 65599 * hash + ch;
//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
};
template<class T>
struct RSHash
{
size_t operator()(const T &str)
{
size_t hash = 0;
size_t magic = 63689;
for(auto chi :str)
{
size_t ch = (size_t)chi;
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
};
template<size_t N,//需要存储的最大数据!
size_t X= 5,//平均存储一个值,开辟X位的空间!
class K = std::string,
class HashFunc1 = BKDRHash<K>,
class HashFunc2 = SDBMHash<K>,
class HashFunc3 = RSHash<K>>//哈希函数个数和玻璃长度有关系!
class BloomFilter
{
public:
void set(const K& key)
{
size_t hash1 = HashFunc1()(key) % (X*N);//要加上()因为 % 优先级高于*
size_t hash2 = HashFunc2()(key) % (X*N);
size_t hash3 = HashFunc3()(key) % (X*N);
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
bool test(const K& key)
{
size_t hash1 = HashFunc1()(key) % (X*N);
if(!_bs.test(hash1))
return false;
size_t hash2 = HashFunc2()(key) % (X*N);
if(!_bs.test(hash2))
return false;
size_t hash3 = HashFunc3()(key) % (X*N);
if(!_bs.test(hash3))
return false;
//前面判断不在是不准确的!
return true;//可能存在误判!映射的几个位置都冲突了!
}
private:
std::bitset<N*X> _bs;
};
}
关于要几个哈希函数,和布隆过滤器的长度有关!
详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)关于想在这个方面更加的深入可以看这篇文章
==过滤器是用来减低误判的!不是用来避免误判的!==
==这个不支持reset!因为一个位置可能会被多个值给映射!如果reset掉可能就会影响其他的查找!==
如果我们把find给reset了!那么我们就会影响Insert!
测试
#pragma once
#include <iostream>
#include <bitset>
#include <string>
namespace MySTL
{
void test_bloomfilter()
{
srand(time(0));
const size_t N = 100000;
BloomFilter<N> 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;
}
}
==我们可以通过增大X与增加哈希函数个数用来减少误判率!==