C++----bitmap位图的使用
位图(Bitmap)是一种用二进制位(bit)来表示数据状态的存储结构,常用于节省内存、进行快速查找、去重或集合操作。
核心概念
位图的思想很简单:
用一个二进制位代表一个元素的“存在与否”。
1 表示某个数存在;
0 表示某个数不存在。
例如要存储 [1, 3, 5]
:
下标(表示数字) | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
位状态 | 0 | 1 | 0 | 1 | 0 | 1 |
这样我们只用了 6 位(二进制)就能表示 6 个数的存在性。
典型用途
大规模数据去重
判断某个数是否出现过:O(1)
内存比
unordered_set
、map
小得多。
快速排序辅助
只需扫描位图的每一位,即可得到排序结果(天然有序)。
布隆过滤器(Bloom Filter)底层实现
位图配合多个哈希函数实现高效的“近似存在”判断。
权限管理 / 状态压缩
用每一位代表某种状态或权限标记。
优点 | 缺点 |
---|---|
占用内存极小 | 只能表示“是否存在”,不能存储实际值 |
查找速度极快 | 只能用于整数或能映射成整数的值 |
可用于排序和去重 | 数据范围过大时依然可能超内存(如 10¹² 级别) |
面试题
1.给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
第一个思路,遍历,虽然复杂度是O(N),但40亿不是小数目;第二种用红黑树,set解决,虽然他的插入和搜索很快,O(logN),但是建树很慢,约等于N*O(logN),也很大了;第三种排序+二分搜索,虽然搜索可以做到O(logN),但是排序最少也是O(N*logN),还是比较大的;同时,就算可以用上述的方式做到,40亿个整型数据,大概要占4000000000 个 int×4 字节=16000000000 字节÷1024÷1024÷1024≈14.9GB,试问一般的计算机内存大都是8GB,也有16GB的,但是总不能把空间都开给这个程序吧。所以这里就用到位图,可以缩小到八分之一,减小内存的消耗。
模拟实现,这里用到了位运算,大家可以自己动手理一理。通过对存放int的vector进行处理,set就是置1,reset置0,test检查是否存在X这个数字。这里还用到了非类型模板参数,在编译时可以确定参数的大小。注意resize中的数字正是为vector中存储的数据所开辟的个数,所以这里要注意,传入的N是比特位数,除以32就是int的数量。
template<size_t N>class bitmap{public:bitmap(){_bm.resize(N/32+1,0);}void set(int x){int i = x / 32;int j = x % 32;_bm[i] |= 1 << j;}void reset(int x){int i = x / 32;int j = x % 32;_bm[i] &= (~(1 << j));}bool test(int x){int i = x / 32;int j = x % 32;return _bm[i] & (1 << j);}private:vector<int> _bm;};
2.给定100亿个整数,设计算法找到只出现一次的整数?1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。
这个题可以用两个位图来处理,我们先假设为每个值分配两个比特位,00代表不存在,10代表出现一次,01代表出现两次,11代表出现两次以上。那么是否要使用连续的两个比特位呢?不太好处理,那就用现成的位图:这个处理的是第一个问题,第二个问题可以扩展,请读者自行完成
template<size_t N>class two_bitmap{public:void set(int x){//00一次也未出现if (!_bm1.test(x) && !_bm2.test(x)){_bm1.set(x);}//10出现了一次 设置成01else if (_bm1.test(x) && !_bm2.test(x)){_bm1.reset(x);_bm2.set(x);}//两次及两次以上else;}bool is_once(int x){return _bm1.test(x) && !_bm2.test(x);}private:bitmap<N> _bm1;bitmap<N> _bm2;};
3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
要考虑到两个文件会中各自会有重复的文件,所以将两个文件考虑合并成一个文件,再用上面题2的处理方式是不行的。那么可以各自对应一个位图进行处理,数据存在的对应位置设置成1,不必理会是否重复。然后再对各自的位图进行检查:
//假设a和b是两个不同的文件int a[] = { 1,2,3,4,5,6,7,1 };int b[] = { 2,4,6,8,3,1,0,8,9,4 };wzz::bitmap<10> b1;wzz::bitmap<10> b2;for (auto e : a){b1.set(e);}for (auto e : b){b2.set(e);}//利用set去重wzz::unorder_set<int> s1;for (auto e : a){if (b2.test(e))s1.insert(e);}cout << "交叉文件有:" << endl;for (auto e : s1){cout << e << " ";}