位图
概念
背景:现在有这样一道题,给 40 亿个不重复的未排序的无符号整数,输入一个无符号整数,请你判断一下这个数是否在这 40 亿个数中; 这个题的解法可以有很多种:直接遍历查找、先排序再二分查找等等,但是有一个问题,那就是你这些操作的前提是要先将这些数据存入内存中,但是这么庞大的数据肯定是放不下的,那么你该怎么办呢? 其实我们并不需要存储数据的完整值,我们只需要标记一下这个数是否存在,存在或者不存在这是两个状态,因此一个比特位就可以完成这件事; 概念:所谓位图,就是使用内存中的每一个比特位来存放数据的状态(0——状态 1,1——状态 2),适用于海量数据、数据无重复的场景,通常是用来判断某个数据存不存在(0——不存在,1——存在); 性质:位图中的每一个比特位代表了一个数据,这是一 一对应的关系,不存在冲突,所以也可以看做是直接定址法; 使用
首先确定环境,在 32 位的环境下,整型数据 int 占据 32 位,所以一个整数可以记录 32 个数据的状态; 其次我们需要确定数据的位置,一个数据就需要一个比特位,所以先获取所有数据中的最大值 max,此时我们就需要创建 max 个比特位; max 个比特位至少需要 needint = max / 32 + 1 个整型数据才能表示,所以创建一个大小为 needint 数组来表示位图; 数组下标为 0 的整数的每一比特位可以依次表示 0 ~ 31 这些数据的状态,数组下标为 1 的整数的每一比特位可以依次表示 32 ~ 63 这些数据的状态,以此类推;
实现
class BitSet {
public :
BitSet ( const size_t& count)
: _bit ( count / 32 + 1 )
, _bitcount ( count)
{ }
void set ( size_t num) {
if ( num > _bitcount)
return ;
int idx1 = num / 32 ;
int idx2 = num % 32 ;
_bit[ idx1] = _bit[ idx1] | ( 1 << idx2) ;
}
bool test ( size_t num) {
if ( num > _bitcount)
return false ;
int idx1 = num / 32 ;
int idx2 = num % 32 ;
return _bit[ idx1] & ( 1 << idx2) ;
}
void reset ( size_t num) {
if ( num > _bitcount)
return ;
int idx1 = num / 32 ;
int idx2 = num % 32 ;
_bit[ idx1] = _bit[ idx1] & ~ ( 1 << idx2) ;
}
size_t count ( ) const {
return _bitcount;
}
private :
vector< int > _bit;
size_t _bitcount;
} ;
布隆过滤器
概念
背景:我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,不过它每次推荐时要进行去重,就是去掉那些已经看过的内容,那么问题来了,新闻客户端推荐系统如何实现推送去重的? 做法:用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录,可是该如何快速查找呢?
用哈希表存储用户记录,缺点:浪费空间; 用位图存储用户记录,缺点:不能处理哈希冲突; 将哈希与位图结合,即布隆过滤器; 概念:布隆过滤器是由布隆(Burton Howard Bloom)在 1970 年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在 或者可能存在 ”,它是用多个哈希函数,将一个数据映射到位图结构中,此种方式不仅可以提升查询效率,也可以节省大量的内存空间; 简言之,就是用 n 个哈希表对每一个数据计算出 n 个位置,然后将这 n 个位置对位图总容量取余得到 n 个新的位置,再将位图中这 n 个新的位置置为 1 即可; 性质:布隆过滤器其实就是相当于是除留余数法的哈希表,只是这个表中存放的不是数据,而是数据的状态,并且数据的位置并不是由一个哈希函数确定,而是由多个哈希函数共同确定; 缺点:
可能会造成误判,当我们判断一个数据是否存在时,先对数据通过哈希函数(假设有三个)计算出位置,假设这些位置为 d1、d2、d3,如果这三个位置中只要有一个等于 0,那么则说明这个数据不存在,这是可以确定的,但是如果这三个位置全为 1,那么就能说明它存在吗? 这是不可以的,因为可能会有其他数据最终使得这三个位置变为 1,所以这是不能确定的,不过这种情况的几率很小,布隆过滤器可以保证绝大多数情况是正确的,所以可以放心使用; 布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素,一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给 k 个计数器 ( k 个哈希函数计算出的哈希地址) 加一,删除元素时,给 k 个计数器减一,通过多占用几倍存储空间的代价来增加删除操作,这十分的麻烦; 优点:
增加和查询元素的时间复杂度为:O(K) (K 为哈希函数的个数,一般比较小),与数据量大小无关; 哈希函数相互之间没有关系,方便硬件并行运算; 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势; 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势; 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能; 使用同一组散列函数的布隆过滤器可以进行交、并、差运算;
实现
struct HFun1
{
size_t operator ( ) ( const std:: string& str)
{
size_t hash = 0 ;
for ( auto & ch : str)
{
hash = hash * 131 + ch;
}
return hash;
}
} ;
struct HFun2
{
size_t operator ( ) ( const std:: string& str)
{
size_t hash = 0 ;
for ( auto & ch : str)
{
hash = hash * 65599 + ch;
}
return hash;
}
} ;
struct HFun3
{
size_t operator ( ) ( const std:: string& str)
{
size_t hash = 0 ;
size_t magic = 63689 ;
for ( auto & ch : str)
{
hash = hash * magic + ch;
magic *= 378551 ;
}
return hash;
}
} ;
template < class T , class HFun1 , class HFun2 , class HFun3 >
class BloomFilter
{
public :
BloomFilter ( size_t number)
: _bitCount ( 5 * number)
, _bs ( _bitCount)
{ }
void Set ( const T& data) {
int index1 = HFun1 ( ) ( data) % _bitCount;
int index2 = HFun2 ( ) ( data) % _bitCount;
int index3 = HFun3 ( ) ( data) % _bitCount;
_bs. set ( index1) ;
_bs. set ( index2) ;
_bs. set ( index3) ;
}
bool Find ( const T& data) {
int index1 = HFun1 ( ) ( data) % _bitCount;
int index2 = HFun2 ( ) ( data) % _bitCount;
int index3 = HFun3 ( ) ( data) % _bitCount;
if ( ! _bs. Find ( index1) || ! _bs. Find ( index2) || ! _bs. Find ( index3) ) {
return false ;
}
return true ;
}
private :
BitSet _bs;
size_t _bitCount;
} ;
海量数据问题
哈希应用
位图应用
布隆过滤器