文章目录
- 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);