位图和布隆过滤器
当我们对于搜索结构有一定的认识后,我们知道了当我们需要搜索一个数据时,可以二分查找,当让这是较为原始的查找方式,进一步我们掌握了二叉树,学会了利用搜索树查找,可是当数据量为100亿时,我们不可能通过排序然后搜索树查找,进一步我们学会了除了排序的另一种方式映射,从而掌握了哈希查找,可是当数据为100亿时,可能所有的整数都出现了一遍,这意味着我们要开42亿9千万个int空间来存储,大约有16GB大小,这又太过于浪费空间。于是我们想出新的办法来解决问题。
位图
一般来说数据量足够大时,只需要判定存在与否即可,那一个位置仅仅需要存在与不存在两种状态即可,针对这点我们想出了通过位来节省空间保存数据,一个字节的每一个位都表示了一个数据的存在状态,这样的话一个int可以有32个状态,我们保存所有的整数范围仅仅需要512MB即可,大大的节省了空间。
我们可以用下图进一步说明位图的情况:
首先我们先需要确定需要存储数据的范围,例如我们一共需要35个数据存储,那么一个int可以表示32个状态,我们开两个int即可。找到所表示数据的状态时先找到他在第几个int例如我们在35个存储数据中找12,首先确定他在第一个int,然后他在第12个位置,更进一步说他在第二个字节的第四个位置,我们再操纵位运算更改状态即可。
接下来我们给出具体的实现方法:
首先定义位图的结构体,并且在初始化中给位图开空间
typedef struct BitMap
{
size_t* _bits;
size_t _range;
}BitMap;
void BitMapInit(BitMap* bm, size_t range)
{
bm->_bits = (size_t*)malloc(sizeof(size_t)*((range>>5)+1));
bm->_range = range;
memset(bm->_bits,0, sizeof(rsize_t)*((range >> 5) + 1));
}
接下来这两个接口表示我们需要在第x个位置将状态置为存在与不存在,按照前文所说找到位置操纵位运算即可。
void BitMapSet(BitMap* bm, size_t x)
{
size_t index = bm->_range/x;
int num = bm->_range%x;
bm->_bits[index] |= (1 << num);
}
void BitMapReset(BitMap* bm, size_t x)
{
size_t index = bm->_range / x;
int num = bm->_range%x;
bm->_bits[index] &= ~(1<<num);
}
有的时候我们还需要查看某些位置的状态,依旧是操纵位元算即可
int BitMapTest(BitMap* bm, size_t x)
{
size_t index = bm->_range / x;
int num = bm->_range%x;
if ((bm->_bits[index] >> num) & 1 == 1)
{
return 0;
}
else
{
return 1;
}
}
布隆过滤器
位图可以节省空间存储更多的值,可是他只能局限在整数范围内,因为整数是有范围的,再多的数我们也可以用512MB搞定,可是如果我们给的是字符串呢,字符串是没有范围的,无穷无尽的,这个时候我们需要布隆过滤器,布隆过滤器每一个位置用来表示字符串的存在状态,我们也不用次次开512MB空间,因为没有意义了,我们只需要开字符串需要的空间即可,可是又有了新的问题,字符串哈希算法是将字符串转变成整数的算法,然而字符串是无穷的,整数是有穷的,这样就会有很多不同的字符串结果通过该算法转变成的整数值相等,当我们想要找到A字符串时会找到B字符串,而当我们想要插入C字符串时发现已经存在A字符串等很多问题,于是我们想出了新的办法,我们可以通过三种映射方式来映射,每一个字符串在这个位图中给他三个位置,若插入这个字符串那计算得到的三个位置皆赋1,毕竟两个字符串之间三个映射位置全相等的概率基本为0.这样就要求我们开更多的空间,经过研究表明,我们开位图的4.5倍空间最为合适。
接下来给出布隆过滤器的接口实现:
首先定义结构体,初始化这个布隆过滤器并且定义三种映射方式
#include"BitMap.h"
typedef const char* KeyType;
typedef size_t (*HASH_FUNC)(KeyType str);
HASH_FUNC _hashfunc1(KeyType str)
{
size_t seed = 13;
size_t hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (HASH_FUNC)(hash & 0x7FFFFFFF);
}
HASH_FUNC _hashfunc2(KeyType str)
{
size_t seed = 131;
size_t hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (HASH_FUNC)(hash & 0x7FFFFFFF);
}
HASH_FUNC _hashfunc3(KeyType str)
{
size_t seed = 1313;
size_t hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (HASH_FUNC)(hash & 0x7FFFFFFF);
}
typedef struct BloomFilter
{
BitMap _bm;
HASH_FUNC _hashfunc1;
HASH_FUNC _hashfunc2;
HASH_FUNC _hashfunc3;
}BloomFilter;
void BloomFilterInit(BloomFilter* bf, size_t range)
{
BitMapInit(&bf->_bm,range*4);
}
通过操纵位元算给布隆过滤器赋状态
{
size_t index = bf->_hashfunc1(key)/bf->_bm._range;
int num = bf->_hashfunc1(key) % bf->_bm._range;
bf->_bm._bits[index] |= (1 << num);
size_t index = bf->_hashfunc2(key) / bf->_bm._range;
int num = bf->_hashfunc2(key) % bf->_bm._range;
bf->_bm._bits[index] |= (1 << num);
size_t index = bf->_hashfunc3(key) / bf->_bm._range;
int num = bf->_hashfunc3(key) % bf->_bm._range;
bf->_bm._bits[index] |= (1 << num);
}
由于布隆过滤器有三个映射位置,删除的话将会影响其他字符串的存储,所以布隆过滤器没有删除。
找到该字符串是否存在:看三个映射位置的状态是否都为1
int BloomFilterTest(BloomFilter* bf, KeyType key)
{
size_t index = bf->_hashfunc1(key) / bf->_bm._range;
int num = bf->_hashfunc1(key) % bf->_bm._range;
int i = (bf->_bm._bits[index] >> num) & 1;
size_t index = bf->_hashfunc2(key) / bf->_bm._range;
int num = bf->_hashfunc2(key) % bf->_bm._range;
int j = (bf->_bm._bits[index] >> num) & 1;
size_t index = bf->_hashfunc3(key) / bf->_bm._range;
int num = bf->_hashfunc3(key) % bf->_bm._range;
int k = (bf->_bm._bits[index] >> num) & 1;
if (i + j + k == 3)
{
return 0;
}
else
{
return -1;
}
}