1.位图概念
位图概念:所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景通常是用来判断某个数据存不存在的。
2.面试题引出位图
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】
解决思路:
首先分析40亿个不重复的整数加载到内存需要的空间大小
1G=10^9=10亿字节
一个整数=4个字节
4G=40亿字节=10亿整数
所以40亿整数大概需要16G的空间,一般的查找思路如排序二分查找在内存上肯定是行不通。
因为我们只需要判断这个数字是否在40亿个数中即可,所以为了节约空间,可以用一个1个比特位来表示这个数字在不在。
一个int有32个比特位,之前一个int对应一个整数,现在一个int可以标记32个整数,节约了空间。所用空间大概为16G/32=500M。方案可行
C++实现简单的位图
#pragma once
#include<vector>
#include<cassert>
using namespace std;
namespace NUC
{
template<size_t Size>//Size表示开几位
class BitSet
{
public:
BitSet() {
_bits.resize((Size / (4 * 8)) + 1, 0);//多开一个整数,保证所有整数都可以映射到
}
void Set(size_t x)//把x映射的位标记为1
{
assert(x < Size);
//先计算x在那个整数上
size_t index_int = x / 32;
//计算x在哪一位
size_t index_bit = x % 32;
_bits[index_int] |= (1 << index_bit);
}
void ReSet(size_t x)//把x映射的位标记为0
{
assert(x < Size);
//先计算x在那个整数上
size_t index_int = x / 32;
//计算x在哪一位
size_t index_bit = x % 32;
_bits[index_int] &= (~(1 << index_bit));
}
bool Find(size_t x)//查找x是否在位图上,true为存在
{
assert(x < Size);
//先计算x在那个整数上
size_t index_int = x / 32;
//计算x在哪一位
size_t index_bit = x % 32;
//判断第index_bit为是否为1
return ((_bits[index_int] >> index_bit) & 1) == 1;
}
private:
vector<int>_bits;
};
}
测试
#include"BitSet.h"
#include<iostream>
using namespace std;
void Test()
{
NUC::BitSet<-1>bits;//-1提升到size_t大小为2^32-1大约为42亿
long long int NumTest = 18734;
bits.Set(NumTest);
if (bits.Find(NumTest))
{
cout << "YesSet" << endl;
}
bits.ReSet(NumTest);
if (bits.Find(NumTest) == false)
{
cout << "YesyReSet" << endl;
}
}
int main()
{
Test();
return 0;
}
输出结果:
所占内存大概为500MB
3.位图的变形题
- 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
方案一:
把第一个文件的整数映射到一个位图中,再读取另一个文件的所有整数,判断在不在位图中,在就是交集中的数,否则不在。
注意:100亿个整数肯定存在重复,整数最大为2^32个大小为500MB
方案二:
把第一个文件的整数映射到一个位图1中,再读取另一个文件的所有整数映射到一个位图2中,
在对两个位图进行&,如果结果位图位为1证明是文件交集。所用内存为1G
- 给定100亿个整数,设计算法找到只出现一次的整数?
标记一个整数出现次数的状态:(用两个位标记一个整数)
出现0次:00
出现1次:01
出现2次及其以上:10
使用两个位图,bits1与bits2
出现一次bits1设置1,出现2次bits2设置1
void Set(size_t N)
{
NUC::BitSet<-1>bits1;
NUC::BitSet<-1>bits2;
//00 ->01
if (!bits1.Find(N) && !bits2.Find(N))//出现一次
{
bits2.Set(N);
}
else if (!bits1.Find(N) && bits2.Find(N))//01->10,出现2次
{
bits1.Set(N);
bits2.ReSet(N);
}
else if(bits1.Find(N) && !bits2.Find(N))//10 ->10,出现2次以上
{
bits1.Set(N);
bits2.ReSet(N);
}
else//发生错误
{
assert(false);
}
}