文章目录
- 1.位图的概述与实现
- 1.1.位图的引出与概述
- 1.2.位图的代码实现
- 1.3.位图的应用及其他面试题
1.位图的概述与实现
当然C++库中也有位图的实现:链接
1.1.位图的引出与概述
面试题:给40亿个不重复的无符号整数(0~2^32),没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中
能不能,将40亿个排序 + 二分查找,又或者整数存放在map或set,理论上是可以的只要你的内存足够大!无符号整数在32位机器下要用32个比特位来存放,我们按40个比特位算40亿个需要160亿个字节,那么1G大概是10亿个字节,存放40亿个无符号整数要16G左右的内存空间,普通配置的电脑是存放不下的!
能不能一个整数对应用一个比特位来标定
32位标定一个整数的状态是在不在,太浪费,我们可以使用1个比特位通过哈希的直接定址法来标定整数的状态。1G是80亿个比特位,那么40亿个无符号整数大概就是0.5G的内存。
这里就是所谓的位图 ,就是用每一位来存放某种状态,适用于海量数据场景。通常是用来判断某个数据存不存在的。
1.2.位图的代码实现
位图的实现
一个位图中可以使用vector<int>
来存放,40亿有很多个int
,我们可以是使用 n / 32
找到整数n
存放第几个int
位置,再通过n % 32
找到存放在哪个比特位上,如果是添加,将对应的比特位通过位运算将其置成1即可,如果是删除将对应的比特位通过位运算将其置成0即可。
添加操作
将对应的比特位置成1即可
void set(int n)
{size_t index = n / 32;size_t bit_index = n % 32;_bitset[index] |= (1 << bit_index);
}
下面假设要将下标为2的比特位置成1:
让1左移2位,然后或运算,或等是为了改变状态。这里不用考虑大小端的问题,1左移是向高位移动,至于大端还是小端怎么样是机器内部的事!删除和查找是类似的!
删除操作
void reset(int n)
{size_t index = n / 32;size_t bit_index = n % 32;_bitset[index] &= (~(1 << bit_index));
}
查找操作
bool test(int n)
{size_t index = n / 32;size_t bit_index = n % 32;return _bitset[index] & (1 << bit_index);
}
完整代码
#include<vector>
#include<iostream>
namespace xiYan
{template<size_t N>class bitset{public:bitset() {_bitset.resize(N, 0);}void set(int n) {size_t index = n / 32;size_t bit_index = n % 32;_bitset[index] |= (1 << bit_index);}bool test(int n) {size_t index = n / 32;size_t bit_index = n % 32;return _bitset[index] & (1 << bit_index);}void reset(int n) {size_t index = n / 32;size_t bit_index = n % 32;_bitset[index] &= (~(1 << bit_index));}private:std::vector<int> _bitset;};
}// 测试代码
#include"bitset.h"
using namespace std;void main() {// 32位大概是43亿个无符号整数,如果是40亿个,我们直接开辟sizt_t的最大值(-1)即可// xiYan::bitset<-1> bs;xiYan::bitset<100> bs;int arr[] = { 1,7,4,3,22,9,7 };for (auto num : arr) {bs.set(num);}cout << bs.test(7) << endl;cout << bs.test(17) << endl;;bs.reset(7);cout << bs.test(7) << endl;return 0;
}
1.3.位图的应用及其他面试题
- 给定100亿个整数,设计算法找到只出现一次的整数
需要找到出现一次的整数,说明1个比特位标记在不在的状态是不行了!我们可以考虑用两个比特位来标识00 01 10 11
其中01标识只出现一次。
用一个位图中的两个比特位来标识。
用两个位图对应的比特位来表示,显然是方式2好,可以直接复用位图的结构!方式1要考虑如何切分出两个比特位还需要重新写一遍代码,麻烦些。
方式2的完整代码
#pragma once
#include<vector>
#include<iostream>
#include<bitset>namespace xiYan
{template<size_t N>class towBitset{public:void set(int n){if (!_one.test(n) && !_tow.test(n)) {_one.set(n);}else if (_one.test(n) && !_tow.test(n)) {_tow.set(n);_one.set(n);}else {return;}}bool test(int n) {return (_one.test(n) && !_tow.test(n));}private:std::bitset<N> _one;std::bitset<N> _tow;};
}// 测试代码
#include"bitset.h"
using namespace std;void test2()
{xiYan::towBitset<100> bs;// 这里没考虑负数的情况int arr[] = { 1,7,7 };for (auto num : arr) {bs.set(num);}cout << bs.test(7) << endl;cout << bs.test(2) << endl;cout << bs.test(1) << endl;
}
- 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集
100亿个整数有好多重复的数字,求交集不需要重复的,所以只需要将数据存放到两个位图中,然后对两个位图中的两个对应的位置按位与如果是0则不是交集,非0则是交集。
- 变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
和第一个题相似,只不过,00 01 10 11
则10表示出现两次!
位图的应用
- 快速查找某个数据是否在一个集合中
- 排序 + 去重
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
- 操作系统中文件使用open系统调用的flage选项传入多个参数的时候也使用到了位图
int open(const char *pathname, int flags)
int fd = open("log1.txt",O_WRONLY | O_CREAT | O_TRUNC);