一.位图的基本概念
- 所谓位图,就是用 每一位 来存放某种状态,适用于海量数据,数据无重复的场景。
- 通常是用来判断某个数据存不存在的
二.位图的原理
- 哈希—— 直接定址法
- 例:
- 在实际场景中,我们的机器一般是 小端机(从左到右,从大到小排布)
- 所以真正的场景一般如下:
- 小端机性质 证明:
三.位图(bitset)的代码实现(逐过程解读)
【1】位图的文档查看
- 我们可以重点关注红圈圈出的三个位图常用函数
【2】把X映射的那个标记成1——对应biteset中的set
【3】把X映射的那个标记成0——对应biteset中的reset
【4】判断某位是1还是0——对应biteset中的test
【5】位图的完整实现
#include<vector> namespace bit { template<size_t N> class bitset { public: bitset() { _a.resize(N / 32 + 1);//位图的初始化,确定分为多少块 } // x映射的那个标记成1 void set(size_t x) { size_t i = x / 32; size_t j = x % 32; _a[i] |= (1 << j); } // x映射的那个标记成0 void reset(size_t x) { size_t i = x / 32; size_t j = x % 32; _a[i] &= (~(1 << j)); } bool test(size_t x) { size_t i = x / 32; size_t j = x % 32; return _a[i] & (1 << j); } private: vector<int> _a; }; }
四.位图的经典应用场景
【※】对数据大小&转换的基本概念
- 1G =1024 MB=10241024 BK=10241024*1024 Byte= 2^30 byte = 10亿+ byte
- 例:我们判断40亿个整数需要多少G呢?
分析:40亿个int,160亿byte,根据10亿byte对应1G,160亿byte对应16G
【1】给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?
- 分析:常规思路是遍历/排序+二分查找
- 遍历的时间复杂度是O(N),排序(O(NlogN))+二分查找 logN
- 显然对于40亿无符号整数来说,需要占用16G,占用资源过于庞大,不妥
- 快速判断在不在,显然是位图经典场景,利用位图解决:
- 数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:
【2】给定100亿个整数,设计算法找到只出现一次的整数
- 分析:我们可以用两个位图来控制,我们可以这样设计
- 代码展示设计思路如图所示:
template<size_t N> class twobitset { public: void set(size_t x) { // 00 -> 01 if (!_bs1.test(x) && !_bs2.test(x)) { _bs2.set(x); } // 01 -> 10 else if (!_bs1.test(x) && _bs2.test(x)) { _bs1.set(x); _bs2.reset(x); } // 本身10代表出现2次及以上,就不变了 } bool is_once(size_t x) { return !_bs1.test(x) && _bs2.test(x); } private: bitset<N> _bs1; bitset<N> _bs2; };
【3】位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
- 此题的设计思路与上面的【2】基本一致,设计上要稍作改动:
- 代码展示设计思路如图所示:
template<size_t N> class twobitset { public: void set(size_t x) { // 00 -> 01 if (!_bs1.test(x) && !_bs2.test(x)) { _bs2.set(x); //出现一次 } // 01 -> 10 else if (!_bs1.test(x) && _bs2.test(x)) { _bs1.set(x); _bs2.reset(x); //出现两次 }// 10 -> 11 else if (_bs1.test(x) && !_bs2.test(x)) { _bs2.set(x); //出现三次 } // 此外代表出现3次及以上,就不变了 } bool max_two(size_t x) { return (_bs1.test(x) && !_bs2.test(x))||(!_bs1.test(x) && _bs2.test(x)); //10 或者 01 } private: bitset<N> _bs1; bitset<N> _bs2; };
【4】给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件的交集?
- 分析:
- 第一种思路是:把其中一个文件存入位图,遍历另一个文件元素,将问题转变成"在不在"问题
- 问题缺陷: 这种问题存在去重问题,即多次重复(下图中,交集明明只有一个3,但是会出现多个重复的3交集)
- 分析:
- 第二种思路是:将两个文件映射到两个位图中去(实现去重)
- 如果相对应的位置都是1(满足相&为1),则此元素就在交集中