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,其余同第三题


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 高考体检表填错怎么办 高考体检表没有下载怎么办 警校视力没过怎么办 凉鞋挂钩总是脱怎么办 特别害怕抛妇产怎么办 抛妇产害怕紧张怎么办 毕业体检有乙肝怎么办? 宝宝胸围偏小怎么办 入园体检不合格怎么办 油表不显示油量怎么办 上大学体检不过怎么办 军校体检没过怎么办 六次化疗后怎么办 宝宝肚子有蛔虫怎么办 肾综激素依赖怎么办 胎儿阴囊有积液怎么办 中考学生英语很差怎么办 中考考得很差怎么办 科学总是考不好怎么办 初三学生数学不好怎么办 初三学生语文不好怎么办 初三学生英语不好怎么办 初三学生学习不好怎么办? 客户说不考虑怎么办 单位体检有乙肝怎么办 厂里体检出乙肝怎么办 厂里体检有乙肝怎么办 厂里体检查出乙肝怎么办 乙肝携带者肝功能异常怎么办 入职体检过不去怎么办 高考体检错过了怎么办 听力体检没过怎么办 身上皮肤太黑怎么办 体检手册没有了怎么办 体检来例假了怎么办 体检肝有问题怎么办 体检心脏有问题怎么办 公司体检有问题怎么办 体检如果有问题怎么办 体检肾有问题怎么办 体检肝功能有问题怎么办