目录
1、bitset的介绍
位图的引入
看这样一道面试题:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数, 如何快速判断一个数是否在这40亿个数中。【腾讯】
单纯从判断一个数是否在一串数字的角度看,我们很容易想到下面的方法:
- 把这40亿个整数放到set、unordered_set容器里头,调用find函数来判断
- 把这40亿个整数进行外排序,再去二分查找
单就此题而言,为了推翻上面两种方法,首先,我们要清楚40亿个整数,占用多少空间:
40亿个整数 = 160亿个字节 1GB = 1024MB 1024MB = 1024 * 1024KB 1024 * 1024KB = 1024 * 1024 * 1024Byte ≈ 10亿字节 综上1GB ≈ 10亿字节 16GB ≈ 160亿字节
- 计算得知,40亿整数占16个G内存,光数据就占了16G,若放到set容器,其底层红黑树的内部也有负载的消耗(存颜色,三叉连……),再算上16G的消耗,消耗太大了,内存不够,承受不住。同理,内存不够,数据压根放不到内存,也就不能进行排序。
为了解决此问题,这就需要我们用到位图来解决。
- 我们在判断一个数据是否在给定的整形数据中,结果只有在或者不在这两种状态,那么就可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。这里我们采用直接定址法的哈希,用一个比特位标识映射值在不在,这就是位图。示例:
对于40亿个整数,我们要我们要开整型的最大值(2^32 - 1)个bit位,大概占500MB的内存:
1G = 2^30Byte ≈ 10亿字节 (2^32 - 1)Byte ≈ 40亿字节 ≈ 4G 1Byte = 8bit (2^32 - 1)bit = 4G/8 ≈ 500MB
由此可见,使用位图的方法,大大减少了内存的消耗,并且能很好的解决此问题。
位图的概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
位图的应用
- 快速查找某个数据是否在一个集合中
- 排序 + 去重
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
2、bitset的使用
bitset的构造方式
1、使用默认构造函数,构造一个16位的位图,默认初始化为0
bitset<16> bs1;
2、元素按照给定整数的二进制位进行初始化:0xff ——> 1111 1111
bitset<16> bs2(0xfa2);//0000111110100010
3、使用01的string进行初始化:std::string(“01101001”) ——> 01101001
bitset<16> bs3(string("01101001"));//0000000001101001
4、使用01的字符串进行初始化:(“01101001”) ——> 01101001
bitset<16> bs4("01101001");//0000000001101001
bitset成员函数的使用
bitset常用成员函数如下表格所示:
成员函数 功能 set 设置指定位或所有位 reset 清空指定位或所有位 flip 反转指定位或所有位 test 获取指定位的状态 count 获取被设置位的个数 size 获取可以容纳的位的个数 any 如果有任何一个位被设置则返回true none 如果没有位被设置则返回true all 如果所有位都被设置则返回true 示例:
int main() { bitset<16> bs; bs.set(4); bs.set(6); bs.set(2); cout << bs.size() << endl;//16 cout << bs << endl;//0000000001010100 //获取指定位的状态 cout << bs.test(0) << endl;//0 cout << bs.test(2) << endl;//1 //反转所有位 bs.flip(); cout << bs << endl;//1111111110101011 //反转第1位 bs.flip(1); cout << bs << endl;//1111111110101001 cout << bs.count() << endl;//12 //清空第3位 bs.reset(3); cout << bs << endl;//1111111110100001 //清空所有位 bs.reset(); cout << bs.none() << endl;//1 cout << bs.any() << endl;//0 //设置所有位 bs.set(); cout << bs.all() << endl;//1 return 0; }
注意: 使用成员函数set、reset、flip时,若指定了某一位则操作该位,若未指定位则操作所有位。
bitset运算符的使用
如表格所示:
运算符 功能说明 >>、<< 输入输出运算符 = 赋值运算符 ==、!= 关系运算符 &=、|=、^=、<<=、>>= 复合赋值运算符 ~ 单目运算符 &、|、^ 位运算符 [ ] operator[ ]运算符 示例:
int main() { //>>输入、<<输出运算符 bitset<8> bs; cin >> bs;//10100 cout << bs << endl;//00010100 //复合赋值运算符 bitset<8> bs1("101011"); bitset<8> bs2("100100"); cout << (bs1 >>= 2) << endl;//00001010 cout << (bs2 |= bs1) << endl;//00101110 //位运算符 bitset<8> bs3("10010"); bitset<8> bs4("11001"); cout << (bs3 & bs4) << endl;//00010000 cout << (bs3 ^ bs4) << endl;//00001011 //operator[]运算符 cout << bs3[4] << endl;//1 cout << bs3[2] << endl;//0 }