关于哈希表,有一些扩展的东西需要我们了解。
下面是一道腾讯的笔试题:
给4 0 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这4 0 亿个数中。
这是一道关于海量数据查找的题,其实这道题,我们就可以和哈希表联系在一起,为何说是海量数据呢,对于一个40亿整数,我们如果要存的话,按照无符号整数来存储,那么下来,大概就需要40亿*4这么多字节,下来大概就是16G的内存。
对于现在的64位机,普遍标配内存也就是4-8G的内存,显而易见,16G是没有办法一次性处理的。
所以在这里我们采取一种新的思路,这种思路就是位图
位图是什么?
位图就是bitmap的缩写。所谓bitmap,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。
位图的结构
由于使用了哈希这种结构,原本需要一个整数来存储一位,现在只需要一个比特位就可以表示该数据存在了。
所以,上文中所提到的那道题中,此时用每一位来表示一个数据是否存在,那么此时只需要4G内存,显然大大降低内存的消耗。
位图的实现
位图实现以下操作:
- 某一位置0
- 某一位置1
- 所有位置0
- 所有位置1
- 测试某一位是0或1
位图定义如下:
typedef struct BitMap {
BitMapType* data;
uint64_t capacity;//代表位图总位数
} BitMap;//定义位图
void BitMapInit(BitMap* bm);//初始化位图
void BitMapSet(BitMap* bm, uint64_t index);//设置某一位为1
void BitMapUnSet(BitMap* bm, uint64_t index);//设置某一位为0
int BitMapTest(BitMap* bm, uint64_t index);//测验某一位是不是1
void BitMapDestroy(BitMap* bm);//销毁位图
void BitMapFill(BitMap* bm);//全部置为1
void BitMapEmpty(BitMap* bm);//全部置为0
uint64_t GetSize(uint64_t index)
{
return index/(sizeof(BitMapType)*8)+1;
}
void BitMapInit(BitMap* bm)
{
if(bm==NULL)
{
return;
}
bm->capacity=BitMapSize;
uint64_t size=GetSize(bm->capacity);
bm->data=(BitMapType*)malloc(sizeof(BitMapType)*size);
memset(bm->data,0,sizeof(BitMapType)*size);
}
int BitMapTest(BitMap* bm, uint64_t index)
{
if(bm==NULL)
{
return -1;
}
uint64_t offset=GetSize(index);
uint64_t size=index%(sizeof(BitMapType)*8);
uint64_t ret=(bm->data[offset]&(0x1ul<<size));
return ret>0?1:0;
}
void BitMapSet(BitMap* bm, uint64_t index)
{
if(bm==NULL)
{
return;
}
if(index>bm->capacity)
{
return;
}
uint64_t offset=GetSize(index);
uint64_t size=index % (sizeof(BitMapType)*8);
bm->data[offset]|=(0x1ul<<size);
}
void BitMapUnSet(BitMap* bm, uint64_t index)
{
if(bm==NULL)
{
return;
}
if(index>bm->capacity)
{
return;
}
uint64_t offset=GetSize(index);
uint64_t size=index % (sizeof(BitMapType)*8);
bm->data[offset]&=~(0x1ul<<size);
}
void BitMapDestroy(BitMap* bm)
{
if(bm==NULL)
{
return;
}
bm->capacity=0;
free(bm->data);
bm->data=NULL;
}
void BitMapFill(BitMap* bm)
{
if(bm==NULL)
{
return;
}
memset(bm->data,0xff,bm->capacity);
}
void BitMapEmpty(BitMap* bm)
{
if(bm==NULL)
{
return;
}
memset(bm->data,0x0,bm->capacity);
}
布隆过滤器
布隆过滤器是哈希表的另一个扩展应用。
关于布隆过滤器,首先我们可以知道这个东西是个叫做布隆的人发现的,它一般用于解决网络爬虫重复问题,还有比如防垃圾邮件或垃圾网页的实现,都可以利用它。
布隆过滤器也是针对的是哈希冲突,可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。
通俗点说也就是本来是一个位表示存在还是不存在,为了方便解决冲突问题,我们在这里采用多个位来表示一个数的状态,在这有个特别重要的特点:布隆中不存在是确定的,存在是不一定的,因为多个位置表示一个数,如果这些位当中有一位是0,那么这个数一定是不存在的,但是如果这个数对应的位置都是1,不一定代表他存在,因为也可能是其它数的位是1,影响了它。
布隆过滤器的优缺点
布隆的优点:
空间效率和查询时间都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(1)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,因为它所存储的是它数据的状态,在某些对保密要求非常严格的场合有优势。
布隆过滤器的缺点:
误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表就足够了。
布隆过滤器的用例
- Google著名的分布式数据库BigTable使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的i/o次数。
- Squid网页代理缓存服务器在cache digests中也使用了布隆过滤器。
- Venti文档存储系统也采用布隆过滤器来检测先前存储的数据。
- SPIN模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。
- Google Chrome浏览器使用了布隆过滤器加速安全浏览服务。
实现布隆过滤器
- 插入一个字符串
- 判断一个字符串是否存在
不支持删除。
并且引入多个哈希函数来降低哈希冲突。
关于字符串的哈希函数可见:
各种字符串Hash函数
布隆过滤器定义如下:
typedef struct BloomFilter{
BitMap bm;
HashFun bloom_hash[BloomHashCount];
}BloomFilter;
void BloomFilterInit(BloomFilter* bf);
void BloomFilterDestroy(BloomFilter* bf);
void BloomFilterInsert(BloomFilter* bf, char* str);
int BloomFilterIsExit(BloomFilter* bf, char* str);
void BloomFilterInit(BloomFilter* bf)
{
if(bf==NULL)
{
return;
}
BitMapInit(&bf->bm);
bf->bloom_hash[0]=HashFun_131;
bf->bloom_hash[1]=HashFun_65599;
}
void BloomFilterDestroy(BloomFilter* bf)
{
if(bf==NULL)
{
return;
}
bf->bloom_hash[0]=NULL;
bf->bloom_hash[1]=NULL;
BitMapDestroy(&bf->bm);
}
void BloomFilterInsert(BloomFilter* bf,char* str)
{
if(bf==NULL||str==NULL)
{
return;
}
size_t i=0;
for(i=0;i<BloomHashCount;i++)
{
uint64_t hash=bf->bloom_hash[i](str) % BitMapSize;
BitMapSet(&bf->bm,hash);
}
}
int BloomFilterIsExit(BloomFilter* bf, char* str)
{
if(bf==NULL||str==NULL)
{
return 0;
}
size_t i=0;
for(i=0;i<BloomHashCount;i++)
{
size_t hash=bf->bloom_hash[i](str) % BitMapSize;
int ret=BitMapTest(&bf->bm,hash);
if(ret==0)
{
return 0;
}
}
return 1;
}