BitMap和BloomFilter
来源:互联网 发布:好看的女包淘宝店铺 编辑:程序博客网 时间:2024/05/16 03:45
今天看到一个面试题给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
首先想到将40亿个数按整型放入内存,但这样显然不科学,就算内存足够,这样做也是浪费空间。这时候想起以前学过的BitMap,
解决思路:用一个比特位表示一个数,存在的话该位上就置为1,不在的话置为0;这样40亿个数需要40亿个Bit,换算一下也就是500M,相对于16G来说,大大节省了空间。
#include<iostream>#include<vector>using namespace std;class BitMap{public:BitMap(size_t range){//_bits.resize((range >> 3) + 1, 0);}void Set(size_t x){int index = x / 8;int bit = x % 8;_bits[index] |= (1 << bit);_size++;}void Reset(size_t x){int index = x / 8;int bit = x % 8;//& 不是|_bits[index] &= (~(1 << bit));_size--;}bool Test(size_t x){int index = x / 8;int bit = x % 8;return (1<< bit) & _bits[index];//_bits[]不能移动 就乱了 自己不动 让别人来试一试}private:vector<char> _bits;size_t _size; //数组中一共存在的数的总数};void TestBitMap(){BitMap bm(-1);bm.Set(7);cout << bm.Test(7) << endl;bm.Set(1);cout << bm.Test(1) << endl;}int main(){TestBitMap();system("pause");return 0;}
位图最大特点是就是节省空间,除过开空间时候消耗时间,查找删除效率特别高。BloomFilter
位图确实非常好,不过任何事情都有两面性,位图它只能处理整型数据,对于字符串就有些不适合了。这个时候就需要用布隆过滤器。
很明显,用位图只能用来处理整型,如果遇到字符型或者其他类型的文件就无能为力了,所以布隆过滤器就上场了。
Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:当一个元素被加入集合时,通过 K个Hash函数
将这个元素映射成一个位阵列(Bit array)中的 K 个点
,把它们置为1
。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:
如果这些点有任何一个 0,则被检索元素一定不在;
如果都是 1,则被检索元素可能在,注意是可能。
优点
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数;另外, Hash 函数相互之间没有关系,方便由硬件并行实现;布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势;布隆过滤器可以表示全集,其它任何数据结构都不能;k 和 m 相同,使用同一组 Hash 函数的两个布隆过滤器的交并差运算可以使用位操作进行。
缺点
布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。另外,一般情况下不能从布隆过滤器中删除元素。 我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。
size_t BKDRHash(const char *str){register size_t hash = 0;while (size_t ch = (size_t)*str++){hash = hash * 131 + ch;}return hash;}size_t SDBMHash(const char* str){register size_t hash = 0;while (size_t ch = (size_t)*str++){hash = 65599 * hash + ch;}return hash;}size_t RSHash(const char * str){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 APHash(const char*str){register size_t hash = 0;size_t ch;for (long i = 0; ch = (size_t)*str++; i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}return hash;}size_t JSHash(const char* str){if (!*str){return 0;}size_t hash = 1315423911;while (size_t ch = (size_t)*str++){hash ^= ((hash << 5) + ch + (hash >> 2));}return hash;}//哈希函数对应的仿函数template<class K>struct __HashFunc1{size_t operator()(const K& key){return BKDRHash(key.c_str());}};template<class K>struct __HashFunc2{size_t operator()(const K& key){return SDBMHash(key.c_str());}};template<class K>struct __HashFunc3{size_t operator()(const K& key){return RSHash(key.c_str());}};template<class HashFunc1 = __HashFunc1, class HashFunc2 = __HashFunc2, class HashFunc3 = __HashFunc3>class BloomFilter{public:void Set(const K& key){_bm.Set(HashFunc1()(key) % _range);_bm.Set(HashFunc2()(key) % _range);_bm.Set(HashFunc3()(key) % _range);_bm.Set(HashFunc4()(key) % _range);_bm.Set(HashFunc5()(key) % _range);}bool Test(const K& key){if (!_bitmap.JudgeBit(HashFunc1()(key) % _range))//只要有一个匹配不上就不存在 return false; if (!_bitmap.JudgeBit(HashFunc2()(key) % _range))return false; if (!_bitmap.JudgeBit(HashFunc3()(key) % _range))return false; if (!_bitmap.JudgeBit(HashFunc4()(key) % _range))return false; if (!_bitmap.JudgeBit(HashFunc5()(key) % _range))return false;}private:BitMap _bm;size_t _range; //};1.如何扩展BloomFilter使得它支持删除元素的操作或者支持计数操作
因为布隆过滤器的一个Key对应多个位,所以如果要删除的话,就会有些麻烦,不能单纯的将对应位全部置为0,因为可能还有其它key对应这些位,所以,需要对每一个位进行引用计数,以实现删除的操作。因为需要每一个对应位都需要一个计数,所以每一位至少需要一个int,那么我们就不得不放弃位图了,也就是放弃了最小的空间消耗,我们需要直接以一个就像数组一样的实现,只不过数组的内容存放的是引用计数,下面给出实现
template<class K = string>class BloomFilter{size_t HashFunc1(const K& key){const char* str = key.c_str();unsigned int seed = 131;unsigned int hash = 0;while (*str){hash = hash*seed + (*str++);}return(hash & 0x7FFFFFFF);};size_t HashFunc2(const K& key){const char* str = key.c_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 HashFunc3(const K& key){const char* str = key.c_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 HashFunc4(const K& key){const char* str = key.c_str();register size_t hash = 0;size_t ch;for (long i = 0; ch = (size_t)*str++; i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}return hash;}size_t HashFunc5(const K& key){const char* str = key.c_str();if (!*str)return 0;register size_t hash = 1315423911;while (size_t ch = (size_t)*str++){hash ^= ((hash << 5) + ch + (hash >> 2));}return hash;}public:BloomFilter(const size_t size){_map.resize(size,0);}void Set(const K& key){size_t hash1 = HashFunc1(key);size_t hash2 = HashFunc2(key);size_t hash3 = HashFunc3(key);size_t hash4 = HashFunc4(key);size_t hash5 = HashFunc5(key);_map[hash1%_map.size()]++;_map[hash2%_map.size()]++;_map[hash3%_map.size()]++;_map[hash4%_map.size()]++;_map[hash5%_map.size()]++;}bool ReSet(const K& key){size_t hash1 = HashFunc1(key);if (_map[hash1%_map.size()] == 0)return false;size_t hash2 = HashFunc2(key);if (_map[hash2%_map.size()] == 0)return false;size_t hash3 = HashFunc3(key);if (_map[hash3%_map.size()] == 0)return false;size_t hash4 = HashFunc4(key);if (_map[hash4%_map.size()] == 0)return false;size_t hash5 = HashFunc5(key);if (_map[hash5%_map.size()] == 0)return false;_map[hash1%_map.size()]--;_map[hash2%_map.size()]--;_map[hash3%_map.size()]--;_map[hash4%_map.size()]--;_map[hash5%_map.size()]--;return true;}bool Test(const K& key){size_t hash1 = HashFunc1(key);if (_map[hash1%_map.size()] == 0)return false;size_t hash2 = HashFunc2(key);if (_map[hash2%_map.size()] == 0)return false;size_t hash3 = HashFunc3(key);if (_map[hash3%_map.size()] == 0)return false;size_t hash4 = HashFunc4(key);if (_map[hash4%_map.size()] == 0)return false;size_t hash5 = HashFunc5(key);if (_map[hash5%_map.size()] == 0)return false;return true;}private:vector<size_t> _map;};3.给定100亿个整数,设计算法找到只出现一次的整数
这道题类似第一道题,只不过它提出的新要求是一定要在100亿中找出出现一次的,这时候之前的做法就需要变动下,因为整数最多就有42亿多,必然这100亿里面是又重复数据的,这里的核心就是需要用两位来表示状态,00表示没有,01表示只出现一次,10表示出现多次。这样最后我们只需要在BitMap里面找所以为01状态的数字就是所求数
4.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集
一、我们可以首先对其中一个文件建立位图,然后依次读取另外一个文件数字,看是否在位图中,最后所有存在的就是交集
二、将两个文件都切分为1000小份,每个文件的大小就几十兆的样子,分别对两个对文件里的整数进行哈希分配,即将所有整数模除1000,使相同的数进入相同的文件,然后分别拿A哈希切分好的第一个文件和B哈希切分好的第一个文件对比,找出交集存到一个新文件中,依次类推,直到2000个文件互相比较完。
5.1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
这个类似第三题 ,只不过这个时候需要多加一个状态,11表示>2,其余同第三题
- BitMap和BloomFilter
- BloomFilter||Bitmap
- 位图BitMap与布隆过滤器BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- bloomFilter
- BloomFilter
- BloomFilter
- BloomFilter
- BloomFilter基本概念和实现原理
- 大数据过滤及判断算法 -- Bitmap / Bloomfilter
- Linux--线程死锁
- stunnel+CCProxy,搭建加密代理
- 【开发日记】马桶型号识别
- CSI相关资料记录
- 个人2017前端面试题汇总
- BitMap和BloomFilter
- 编程第三课 整数类型
- 成功解决小米的线控问题
- 猜数游戏 plus
- express 笔记
- 使用Squid搭建HTTPS代理服务器
- Java 多线程编程核心技术有哪些
- javase数组(最值)
- TestNG介绍与安装