✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:数据结构——位图
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的,其本质也是一种hash。但是其占用空间很少。
目录
编辑
一.位图概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用
来判断某个数据存不存在的。
面试体:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
这40亿个数中。【腾讯】
分析:首先这道题的40亿个无符号整数,如果直接存储到内存的话需要16G的内存,我们首先想到的是使用set,或者unordered_set,但是这里仅仅是数据就已经存储由将近16G大小了,如果我们存放到容器里面,加上一些存储的消耗(指针),实际消耗的内存更是远超16G,这就导致了一般的计算机的内存和普通数据结构来对这些数据进行处理。
所以我们引用一个新的数据结构,就是位图。简单来说仅仅使用一个比特位来标识数据在不在的状态。
- 遍历,时间复杂度O(N)
- 排序(O(NlogN)),利用二分查找: logN
- 位图解决
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一
个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。比如:
编辑
位图也是利用了哈希映射的思想,而且采用的是直接定值映射的方法,最大的区别是位图仅仅使用一个比特位来标识数据是否存在,hash是需要对数据进行存储的。
二.STL库中的位图
STL库中也给我们提供了位图结构bitset,头文件<bitset>
编辑
简单介绍几个核心接口:
- set():将数据插入位图。
- reset():将数据从位图中挪去。
- test():判断数据在不在位图中。
int main() { bitset<10000> bit; int arr1[] = { 1,15,3,6,8,9,4,23 }; int arr[] = { 45,12,32,3,6,35,12,78,94,23,62,54 }; //将arr数据插入位图 for (auto e : arr) { bit.set(e); } //检查arr1,时候出现在位图中 for (auto e : arr1) { cout << e << ":" << bit.test(e) << endl;; } return 0; }
编辑
三.模拟实现位图
C/C++中没有单个比特位类型,最小的也是单个字节的char,所以我们只能使用,char配合vector来存储位图结构。
在定义位图的时候我们可以控制定义的位图存储位数,所以位图的设计我们可以使用一个非类型模板参数来控制。
template<size_t N> class BitSet { public: private: vector<char> _map; };
1.构造函数
假如我们需要开85个比特位,而我们因为最小的开辟单元是char即八比特位,所以如果剩下的不足八比特位,我们仍然按照一个char开辟。
BitSet() { //不足一个字节时,就按一个字节开辟,并且全部初始化0 _map.resize((N / 8) + 1, 0); }
2.set
找到需要插入的比特位共需两步:
- 找到所在char
- 找到所在的比特位
- 使用位运算插入
编辑
void set(const int num) { size_t i = num / 8; size_t j = num % 8; _map[i] |= 1 << j; }
3.reset
reset与set在查找位置时,步骤一致仅仅是在最后一步换成使用位运算将最后一位变为0.
编辑
void reset(const int num) { size_t i = num / 8; size_t j = num % 8; _map[i] &= ~(1 << j); }
4.test
先找到需要判断的位置,在使用位运算判断。
编辑
bool test(const int num) { size_t i = num / 8; size_t j = num % 8; return _map[i] & (1 << j) ; }
四.完整代码
template<size_t N> class BitSet { public: BitSet() { _map.resize((N / 8) + 1, 0); } void set(const int num) { size_t i = num / 8; size_t j = num % 8; _map[i] |= 1 << j; } void reset(const int num) { size_t i = num / 8; size_t j = num % 8; _map[i] &= ~(1 << j); } bool test(const int num) { size_t i = num / 8; size_t j = num % 8; return _map[i] & (1 << j) ; } private: vector<char> _map; };
测试:
编辑
五.位图的引用
- 快速查找某个数据是否在一个集合中
- 排序 + 去重
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
回顾刚才的面试题:
我们将40亿的整数全部存入位图,仅仅才占有512M而已。而且位图一旦插入之后,他的搜索效率非常高。