#include
#include
using namespace std;
void TestBitSet()
{
bitset<100> bs;//一共100个bit位
int array[] = { 1, 3, 7, 4, 12, 16, 19, 13, 22, 18 };
for (auto e : array)
bs.set(e);//将数据映射到位图中
cout << bs.count() << endl;//计算有多少个数据
cout << bs.size() << endl;//bit位总个数
if (bs.test(13))//检验这个元素在不在位图中
cout << "13 is in bitset" << endl;
else
cout << "13 is not in bitset" << endl;
bs.reset(13);//将这个bit位清0
if (bs.test(13))
cout << "13 is in bitset" << endl;
else
cout << "13 is not in bitset" << endl;
}
int main()
{
TestBitSet();
return 0;
}
接下来我们模拟实现一个位图:
#pragma once
#include
#include
#include
using namespace std;
namespace Daisy
{
template//N代表bit位的个数
class bitset
{
public:
bitset()
{
_bs.resize((N >> 3) + 1);//也就是将bit位个数/8+1就是所需字节数
}
//将num的bit位置1
bitset& set(size_t num)
{
assert(num < N);
//计算num在哪一个字节中
size_t index = num>>3;//也就是/8
size_t pos = num % 8;//计算bit位
_bs[index] |= (1 << pos);
return*this;
}
//将num的bit位置0
bitset& reset(size_t num)
{
assert(num < N);
size_t index = num >> 3;
size_t pos = num % 8;
_bs[index] &= ~(1 << pos);//置0就是将1向左移动pos个bit位,然后取反,就只有pos这个位置逻辑与的是0,就置成了0
return *this;
}
bool test(size_t num)const
{
assert(num < N);
//计算num在哪一个字节中
size_t index = num >> 3;//也就是/8
size_t pos = num % 8;//计算bit位
return 0!=(_bs[index] & (1 << pos));
}
size_t size()const
{
return N;
}
size_t count()const//总共有多少个bit位是1
{
int bitCnttable[256] = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2,
3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3,
3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,
3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5,
6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4,
4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,
3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3,
4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6,
6, 7, 6, 7, 7, 8 };//对应的是形如0,1,2,3,4,5的对应bit位有几个,比如0是0个bit位,1是1个比特位,2是1个比特位
size_t szcount = 0;
for (auto e : _bs)
{
szcount += bitCnttable[e];
}
return szcount;
}
private:
vector _bs;//我们使用的是unsigned char是一个字节一个字节的,使用unsigned是为了防止负数的出现,也就是8个bit,如果是整形,就是32个bit了
};
}
void TestBitSet()
{
Daisy::bitset<100> bs;//一共100个bit位
int array[] = { 1, 3, 7, 4, 12, 16, 19, 13, 22, 18 };
for (auto e : array)
bs.set(e);//将数据映射到位图中
cout << bs.count() << endl;//计算有多少个数据
cout << bs.size() << endl;//bit位总个数
if (bs.test(13))//检验这个元素在不在位图中
cout << "13 is in bitset" << endl;
else
cout << "13 is not in bitset" << endl;
bs.reset(13);//将这个bit位清0
if (bs.test(13))
cout << "13 is in bitset" << endl;
else
cout << "13 is not in bitset" << endl;
}
int main()
{
TestBitSet();
return 0;
}
源代码(github):
https://github.com/wangbiy/C-3/tree/master/test_2019_11_18/test_2019_11_18
位图的应用:
快速查找某个数据是否在一个集合中;排序(前提是数据不能重复,将元素映射到位图中之后,就可以回收数据就是有序的);求两个集合的交集、并集等(例如集合1:2 3,集合2:2 4,分别映射到位图中,然后逻辑与之后所得的结果,哪个bit位是1,就是对应的元素,即交集);操作系统中磁盘块标记;
例如:
(1)给定100亿个整数,设计算法找到只出现一次的整数?
100亿整数,使用2bit位统计,需要1.25*2G的内存,先进行哈希切割,切成10份,然后执行以下操作:
使用两个bit位代表一个数据的状态信息,例如:00—数据不存在、01—数据只出现一次、10—数据出现多次;
整个整数数据集合用位图映射,那么2个bit位代表一个数据,一个字节存4个数据,也就是来将位图2个2个bit位来遍历一遍,如果是00,表示没有出现,如果是01,表示第一次出现,如果是10表示出现多次,如果是11也是出现多次
(2)给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
只需要用1bit位来表示,100亿整数需要1.25G的内存,因为两个文件需要2.5G,同样,我们可以哈希切分,分为10份,使用两个位图统计,将位图结果按位与,就可以得到交集。
(3)位图的应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有数据
这个也是使用2个bit位来表示,00—数据不存在、01—数据只出现一次、10—数据出现多次;
#pragma once
#include
#include
using namespace std;
namespace Daisy
{
template < size_t N, class T
class HF1,
class HF2,
class HF3,
class HF4,
class HF5>
class BloomFilter
{
public:
BloomFilter()
:_size(0)
{}
bool Insert(const T& data)
{
size_t index1 = HF1()(data) % N;
size_t index2 = HF2()(data) % N;
size_t index3 = HF3()(data) % N;
size_t index4 = HF4()(data) % N;
size_t index5 = HF5()(data) % N;
_bs.set(index1);
_bs.set(index2);
_bs.set(index3);
_bs.set(index4);
_bs.set(index5);
++_size;
}
bool IsIn(const T& data)
{
size_t index = HF1()(data)%N;
if (_bs.test(index))//如果这个bit位是0,这个数据不在
return false;
index = HF2()(data) % N;
if (_bs.test(index))//如果这个bit位是0,这个数据不在
return false;
index = HF3()(data) % N;
if (_bs.test(index))//如果这个bit位是0,这个数据不在
return false;
index = HF4()(data) % N;
if (_bs.test(index))//如果这个bit位是0,这个数据不在
return false;
index = HF5()(data) % N;
if (_bs.test(index))//如果这个bit位是0,这个数据不在
return false;
return true;//可能在
}
private:
bitset _bs;
size_t _size;
};
}
==注意:==布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
例如:
比如字符串hello计算出来的是1 4 7,它映射到对应的bit位,字符串haha计算出来的是3 4 8,它映射到对应的bit位,那么如果我们查找haha,计算出来是3 4 7,可以找到,但是如果我们找hi,计算出来是1 3 7 ,它和其他元素的bit位重叠,此时布隆过滤器告诉该元素存在,但其实该元素是不存在的。
源代码(github):
https://github.com/wangbiy/C-3/commit/df39c08fd55670c1cbc1b1a677ba0e14580849c4
2、布隆过滤器的删除
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
例如上述要删掉hello,即将它所对应的bit位置0,这时1 4 7 这三个位置都是0,这时“haha”元素也被删除了,产生了问题
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
3、布隆过滤器的优点
(1) 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
(2)哈希函数相互之间没有关系,方便硬件并行运算
(3) 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
(4)在能够承受一定的误判时,布隆过滤器比其他数据结构有着很大的空间优势
(5)数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
(6) 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
4、 布隆过滤器缺陷
(1) 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
(2)不能获取元素本身
(3) 一般情况下不能从布隆过滤器中删除元素
(4) 如果采用计数方式删除,可能会存在计数回绕问题(例如如果是char类型,它的范围是-128-127,如果给127+1就是-127了,产生了回绕)
面试题:
(1)例如:给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
因为我们不确定数据类型,不能直接使用位图,所以我们需要使用布隆过滤器。
经过哈希切割,再布隆,每次两个布隆结果按位与记录,
对于近似算法就是使用普通的布隆过滤器就能做到近似准确得到交集
对于精确算法,将布隆进行扩展,一个数据映射n个位这样虽然占用的空间较大,但是出现误判的几率比较小。
(2)倒排索引
通俗地来讲,倒排索引就是通过value来找key,常被用于全文检索系统中的一种单词文档映射结构,现代搜索引擎绝大多数都是使用倒排索引来进行构建索引的,用户在搜索时查找信息往往只输入信息中的某个属性关键字来进行查找,倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两部分组成:“单词词典”和“倒排文件”。
例如给上千个文件,每个文件大小为1K—100M。给n个词,设计算法对每个词找到所有包含它的文件,你只有100K内存
因为这个题有内存限制,我们将n个单词进行分组—》M组----》每一组对应一个unordered_set—》哈希桶(这样相同的单词被放在同一个哈希桶中),将100k内存分为两份,一份用来存哈希桶,一份用来存放文件(有时候可能内存不够,毕竟只有100k的内存,如果对结果是取一个近似的结果的话,可以将哈希桶换成布隆过滤器);然后读取文件,从文件中获取一个单词word,检测word是否在哈希桶中出现,如果出现,说明单词包含在文件中,否则去下一个哈希桶中找word。