C++语法(23)-- 模拟实现unordered_set和unordered_map_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130449452?spm=1001.2014.3001.5501
目录
1.位图
1.定义
2.实现
3.应用
4.特点
2.布隆过滤器
1.介绍
2.设计场景
3.实现
4.应用
1.位图
1.定义
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
2.实现
要求:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
构造函数的实现:由于有N个数,而char可以存储8位的信息,那么最后有N/8个,不过由于除法会切割后面的多余,所以我们需要对N/8+1处理。
求出在哪一个char中:size_t i = x / 8;
求出在char的哪一位上:size_t j = x % 8;插入的逻辑(或上1左移j位):_bits[i] |= (1 << j);
删除的逻辑(和上1左移j位后取非):_bits[i] &= (~(1 << j));
判断是否在的逻辑:return _bits[i] & (1 << j); 没有则返回0,找到返回非零
namespace MY { template<size_t N> class bit_set { public: bit_set(size_t N) { _bits.resize(N / 8 + 1, 0); } void set(size_t x) { size_t i = x / 8; size_t j = x % 8; _bits[i] |= (1 << j); } void reset(size_t x) { size_t i = x / 8; size_t j = x % 8; _bits[i] &= (~(1 << j)); } bool text(size_t x) { size_t i = x / 8; size_t j = x % 8; return _bits[i] & (1 << j); } private: vector<char> _bits; }; }
3.应用
其实位图的使用很灵活,我们不是非要一个数对应一个比特位这么狭隘;我们可以根据需求,定义一个数对应比特位的多少,;或许用多个位图一起使用,这样就能实现更广的范围。
1. 给定100亿个整数,设计算法找到只出现一次的整数?
我们可以定义两个位:00为0次,01为1次,10为1以上。这样就能实现。
当然使用两个位图也可以实现。
2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?两个先去重,随后用两个位图,如果都有数据说明是交集。
3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
我们可以定义两个位:00为0次,01为1次,10为2次,11为3次以及以上。只需要找01和10即可满足要求。
4.特点
优点:1.节省空间 2.快
缺点:1.一半需要数据比较集中,如果过于分散,空间的消耗会增大 2.只针对整型
2.布隆过滤器
1.介绍
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
布隆过滤器的改进:映射多个位置,降低误判率
2.设计场景
1.注册昵称不允许出现重复的,布隆过滤器用于昵称的查重
2.黑名单列表
3.实现
1.不能实现删除,以为过滤器特殊的误判机制
2.set就是将hashFunc得到的结果映射到位图中,将所有的Func得到位图的结果都进行判1处理
3.test,将所有hash值对应的位图的数据找到,如果没有在位图中说明该数据一定没有出现,如果找到,说明有可能出现误判,那么具体后续的处理还需要设计
4.高效率的设置为:HashFuncN=(N/X)*ln2
HashFuncN:Func函数有几个
N:数据的多少
X:位图的大小
namespace MY { struct BKDRHash { size_t operator()(const string& key) { size_t hash = 0; for (auto ch : key) { hash *= 131; hash += ch; } return hash; } }; struct APHash { size_t operator()(const string& key) { unsigned int hash = 0; int i = 0; for (auto ch : key) { if ((i & 1) == 0) { hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3)); } else { hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5))); } ++i; } return hash; } }; struct DJBHash { size_t operator()(const string& key) { unsigned int hash = 5381; for (auto ch : key) { hash += (hash << 5) + ch; } return hash; } }; struct JSHash { size_t operator()(const string& s) { size_t hash = 1315423911; for (auto ch : s) { hash ^= ((hash << 5) + ch + (hash >> 2)); } return hash; } }; //HashFuncN=(N/X)*ln2 template<size_t N , size_t X = 6, class K = string, class HashFunc1 = BKDRHash, class HashFunc2 = APHash, class HashFunc3 = DJBHash, class HashFunc4 = JSHash> class BloomFilter { public: bool set(const K& key) { size_t hash1 = HashFunc1()(key) % (X * N); size_t hash2 = HashFunc2()(key) % (X * N); size_t hash3 = HashFunc3()(key) % (X * N); _bs.set(hash1); _bs.set(hash2); _bs.set(hash3); return true; } bool test(const K& key) { size_t hash1 = HashFunc1()(key) % (X * N); if (!_bs.test(hash1)) { return false; } size_t hash2 = HashFunc2()(key) % (X * N); if (!_bs.test(hash2)) { return false; } size_t hash3 = HashFunc3()(key) % (X * N); if (!_bs.test(hash3)) { return false; } return true;//可能会出现重复的误判 } private: std::bitset<N * X> _bs; }; }
误判率高,空间开的多就能解决。不过过分追求误判率的低会导致开辟的空间消耗过大。
4.应用
1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出
精确算法和近似算法近似算法:对其中一个文件放入布隆过滤器,另一个进行比较。找出来的交集比真正的交集多,以为会出现误判。
精确算法:假设每个文件的query为50byte,100亿个query有500G。将两个大文件分别切分,按照哈希思路映射(/1000)到单位为0.5G的小文件Ai,Bi。A0文件对应B0文件...A999文件对应B999文件,取出一对文件,两个文件去重后再进行取交集,依次找就能得到精确的交集。
2. 如何扩展BloomFilter使得它支持删除元素的操作其实很简单,之所以不行是因为我们设计的位图只表示存在不存在,因此只有01表示,删除就会出现误判。那么我们只需要设计可以计数的布隆过滤器就能解决可以删除的问题。不过设计出支持删除的布隆过滤器就意味着开辟一个巨大的空间,有时候空间消耗太大反而不值当的。