给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。 html
思路一:ios
最容易想到的解法就是遍历全部的40多亿个整数,而后一个一个判断。可是这个须要花费的内存是多大呢?面试
将40亿数据保存起来(保存在数组、链表、树中),再和该数判断是否相等?算法
那咱们来计算下须要消耗多少内存:数组
40亿 =4000000000数据结构
假如一个数占用一个比特位: 1Byte = 1024MB 1MB = 1024GB分布式
1GB=1024MB =1024*1024KB=1024*1024*1024Byte函数
40000000000/1GB=4000000000/(1024*1024*1024) = 4G测试
但一个数不是占一个比特大数据
所以:须要4*4G = 16G
要用这个方法须要消耗16G内存,如有条件限制,此方法是行不通滴!
思路二:
位图法(BitMap)
是用一个数组中的每一个数据的每一个二进制位表示一个数是否存在。1表示存在,0表示不存在。
至关于把数组分红不少块的空间,每一块是32个比特位。
原来32个比特位放一个数据,至关于如今一个位就能够放一个数据。16GB/32=0.5GB=512MB。
<span style="font-size:14px;">#pragma once #include<iostream> #include <vector> using namespace std; class BitMap { public: BitMap(size_t range) :_size(0) { _bitmap.resize((range>>5)+1); } void Set(size_t x) { size_t index = x>>5;//第几块 size_t num = x%32;//第几位 //将指定位置为一 if(!(_bitmap[index]&(1<<num))) { _bitmap[index] |= 1<<(num); ++_size; } } void Reset(size_t x) { size_t index = x>>5; size_t num = x%32; //价格指定位置为零 if(_bitmap[index]&(1<<num)) { _bitmap[index] &= (~(1<<num)); --_size; } } bool Test(size_t x) { size_t index = x>>5; size_t num = x%32; if(_bitmap[index]&(1<<num)) { return true; } return false; } private: vector<size_t> _bitmap; size_t _size; };</span>
思路三:
布隆过滤器(BloomFilter)
它是由一个很长的二进制向量和一系列随机 映射函数组成,布隆过滤器能够用于检索一个元素是否在一个集合中。那咱们能够利用哈希函数计算出它具体的存放位置。
它的优势是空间效率和查询时间都远远超过通常的算法,将这40亿的数据内存由16GB变成500MB,可见其强大。
缺点是有必定的误识别率、不便于删除。布隆过滤器会出现:检测存在,而实际中却不存在。而不会出现:实际中不存在,而检测存在。
若是想判断一个元素是否是在一个集合里,通常想到的是将集合中全部元素保存起来,而后经过比较肯定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。可是随着集合中元素的增长,咱们须要的存储空间愈来愈大。同时检索速度也愈来愈慢。
Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 能够看作是对 bit-map 的扩展, 它的原理是:
当一个元素被加入集合时,经过 K
个Hash函数
将这个元素映射成一个位阵列(Bit array)中的 K 个点
,把它们置为1
。检索时,咱们只要看看这些点是否是都是 1 就(大约)知道集合中有没有它了:
若是这些点有任何一个 0,则被检索元素必定不在;
若是都是 1,则被检索元素可能在。
若是只是空洞的说这些原理的话,确定你们都不知道布隆过滤器有什么用处。布隆过滤器对于单机来讲可能用处不是很大,但对于分布式来讲就比较有用了。
如主从分布:一个数组过来,我想要知道他是否是在内存中,咱们是否是须要一个一个去访问磁盘,判断数据是否存在。可是问题来了访问磁盘的速度是很慢的,因此效率会很低,若是使用布隆过滤器,咱们就能够先去过滤器这个集合里面找一下对应的位置的数据是否存在。虽然布隆过滤器有他的缺陷,可是咱们可以知道的是当前位置为0是确定不存在的,若是都不存在,就不须要去访问了。
下面来说一下布隆过滤器的缺陷:
缺陷一:误算率(False Positive)是其中之一。随着存入的元素数量增长,误算率随之增长。可是若是元素数量太少,则使用散列表,咱们用多个哈希表去 存储一个数据。那么问题又来了,怎末把握使用多少呢,若是多用哈希表的话,如上面的题,一个哈希就须要500M,那么放的越可能是不是越占内存啊。若是太少的话是否是误算率就高啊,因此取个适中的。下面个人实现是取了五个哈希表(没有什么根据,只是把思路展示出来一下,可以分析出取多少个,那都是大牛们弄出来的算法,我当前水平不够~)
缺陷二:若是当前位置为0确定不存在,可是为1不必定存在。
<span style="font-size:14px;">size_t _GetnewSize(size_t _size) { static const int _PrimeSize = 28; static const unsigned long _PrimeList[_PrimeSize] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (int i = 0; i < _PrimeSize; i++) { if (_PrimeList[i]> _size) { return _PrimeList[i]; } } return _PrimeList[_PrimeSize - 1]; } template<class T> struct __HashFunc1 { size_t BKDRHash(const char *str) { register size_t hash = 0; while (size_t ch = (size_t)*str++) { hash = hash * 131 + ch; // 也能够乘以3一、13一、131三、1313一、131313.. } return hash; } size_t operator()(const T& key) { return BKDRHash(key.c_str()); } }; template<class T> struct __HashFunc2 { size_t SDBMHash(const char *str) { register size_t hash = 0; while (size_t ch = (size_t)*str++) { hash = 65599 * hash + ch; //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash; } return hash; } size_t operator()(const T& key) { return SDBMHash(key.c_str()); } }; template<class T> struct __HashFunc3 { size_t RSHash(const char *str) { register size_t hash = 0; size_t magic = 63689; while (size_t ch = (size_t)*str++) { hash = hash * magic + ch; magic *= 378551; } return hash; } size_t operator()(const T& key) { return RSHash(key.c_str()); } }; template<class T> struct __HashFunc4 { size_t JSHash(const char *str) { if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0 return 0; register size_t hash = 1315423911; while (size_t ch = (size_t)*str++) { hash ^= ((hash << 5) + ch + (hash >> 2)); } return hash; } size_t operator()(const T& key) { return JSHash(key.c_str()); } }; template<class T> struct __HashFunc5 { size_t DEKHash(const char* str) { if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0 return 0; register size_t hash = 1315423911; while (size_t ch = (size_t)*str++) { hash = ((hash << 5) ^ (hash >> 27)) ^ ch; } return hash; } size_t operator()(const T& key) { return DEKHash(key.c_str()); } };</span>
BloomFilter
<span style="font-size:14px;">#include <iostream> using namespace std; #include<string> #include"BitMap.h" #include "Common.h" template<class K = string, class HashFunc1 = __HashFunc1<K>, class HashFunc2 = __HashFunc2<K>, class HashFunc3 = __HashFunc3<K>, class HashFunc4 = __HashFunc4<K>, class HashFunc5 = __HashFunc5<K>> class BloomFilter { public: BloomFilter(size_t size = 0) { _capacity = GetPrimeSize(size); _bitMap.Resize(_capacity); } void Set(const K& key) { size_t index1 = HashFunc1()(key); size_t index2 = HashFunc2()(key); size_t index3 = HashFunc3()(key); size_t index4 = HashFunc4()(key); size_t index5 = HashFunc5()(key); _bitMap.Set(index1%_capacity);//设置为第多少位的数,而后调用位图的Set设置成第几个字节的第几位 _bitMap.Set(index2%_capacity); _bitMap.Set(index3%_capacity); _bitMap.Set(index4%_capacity); _bitMap.Set(index5%_capacity); } bool Test(const K& key) { size_t index1 = HashFunc1()(key); if (!(_bitMap.Test(index1%_capacity)))//为1不必定存在,为0确定不存在 return false; size_t index2 = HashFunc2()(key); if (!(_bitMap.Test(index2%_capacity))) return false; size_t index3 = HashFunc3()(key); if (!(_bitMap.Test(index3%_capacity))) return false; size_t index4 = HashFunc4()(key); if (!(_bitMap.Test(index4%_capacity))) return false; size_t index5 = HashFunc4()(key); if (!(_bitMap.Test(index5%_capacity))) return false; return true; } protected: BitMap _bitMap; size_t _capacity; }; </span>
测试函数;
void TestBloomFilter() { BlooFilter<> bf(10); bf.Set("布隆过滤器"); bf.Test("布隆过滤器"); cout<<"存在?:"<<bf.Test("布隆过滤器")<<endl; bf.Retset("布隆过滤器"); cout<<"存在?:"<<bf.Test("布隆过滤器")<<endl; cout<<"存在?:"<<bf.Test("位图")<<endl; } int main() { TestBloomFilter(); system("pause"); return 0; }