本章代码gitee仓库:位图实现及其应用
文章目录
- 1.位图的概念
- 2. 位图的实现
- 3. 位图应用
1.位图的概念
当面对海量数据时,我们一般的数据结构无法存储那么多的值,要对这些数据进行分析,我们就可以采用位图来对这些数据进行标记(不是存储)。位图适用于海量数据,数据无重复的场景,通常用来判断这个数据是否存在
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】
方法1:
直接遍历,时间复杂度为O(N)
方法2:
先排序(O(N*logN)),再用二分(O(logN))
以上这两种方法,都忽略了,这是40亿无符号整数,差不多需要16G的空间,这对于我们普通的电脑,是行不通的。
那在这种情况下,就可以使用位图解决
如图示例(小端平台):
2. 位图的实现
template<size_t N>
class bitset
{
public:
bitset()
{
_a.resize(N / 32 + 1, 0); //至多多开一个int 空间
}
void set(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
//置1
_a[i] |= (1 << j);
}
void reset(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
//置0
_a[i] &= ~(1 << j);
}
bool test(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
return _a[i] & (1 << j);
}
private:
vector<int> _a;
};
- 这里采用
vector
来管理位集合的数据,int
为4byte
,占32个比特位- 采用非类型模板参数,来表示要开多少个空间,
N/32 + 1
,这里防止开的空间不够,所有每次直接+1
,至多多开一个整型空间
3. 位图应用
- 快速查找某个数据是否在一个集合中
- 排序 + 去重
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
相关题目:
- 给定100亿个整数,设计算法找到只出现一次的整数?
这题我们可以采用2个位图解决,01
表示出现一次,10
表示2次及以上的数据
template<size_t N>
class twobitset
{
public:
void set(size_t x)
{
if (!bt1.test(x) && !bt2.test(x))
{
//出现一次的数据
//00 ->01
bt2.set(x);
}
else if (!bt1.test(x) && bt2.test(x))
{
//2次及以上
//01->10
bt2.reset(x);
bt1.set(x);
}
}
bool is_once(size_t x)
{
return !bt1.test(x) && bt2.test(x);
}
private:
bitset<N> bt1;
bitset<N> bt2;
};
//模拟
void isOnce()
{
bitSet::twobitset<100> tbt;
int arr[] = { 1,2,3,3,44,6,6,4,4,6,3,1,9,6,8 ,3,22 };
for (auto e : arr)
{
tbt.set(e);
}
for (auto e : arr)
{
if (tbt.is_once(e))
cout << e << " ";
}cout << endl;
}
- 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
这题也是采用位图,先将数据映射到位图中,然后再比较这两个位图各位
void IN()
{
bitSet::bitset<32> bt1;
bitSet::bitset<32> bt2;
int arr1[] = { 10,52,20,23,9,22 ,22 ,22 };
for (auto e : arr1)
{
bt1.set(e);
}
int arr2[] = { 11,21,23,14,22,52 };
for (auto e : arr2)
{
bt2.set(e);
}
for (int i = 10; i < 55; i++)
{
if (bt1.test(i) && bt2.test(i))
{
cout << i << " ";
}
}cout << endl;
}
- 1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
这题与第一个类似,我们也是采用两个位图,01
只出现一次,10
出现两次,11
出现2次以上
template<size_t N>
class twobitset
{
public:
void set(size_t x)
{
if (!bt1.test(x) && !bt2.test(x))
{
//00 ->01
bt2.set(x);
}
else if (!bt1.test(x) && bt2.test(x))
{
//01->10
bt2.reset(x);
bt1.set(x);
}
else if (bt1.test(x) && !bt2.test(x))
{
//10->11
bt1.set(x);
bt2.set(x);
}
}
bool is_once(size_t x)
{
return !bt1.test(x) && bt2.test(x);
}
//00
//01
//11
bool lessTwo(size_t x)
{
if ((!bt1.test(x) && bt2.test(x)) || (bt1.test(x) && !bt2.test(x)))
{
bt1.reset(x);
bt2.reset(x);
return true;
}
return false;
}
private:
bitset<N> bt1;
bitset<N> bt2;
};
//模拟
void LessTwo()
{
bitSet::twobitset<10> bt;
int arr[] = { 1,1,2,2,3,3,5,5,5,9,7,7,7,8,8,8,8,1 };
for (auto e : arr)
{
bt.set(e);
}
for (auto e : arr)
{
if (bt.lessTwo(e))
{
cout << e << " ";
}
}cout << endl;
}
C++STL库里面
bitset
支持的接口:
有兴趣的可以查阅文档:bitset
那么本次的分享就到这里,我们下期再见,如果还有下期的话。