目录
1、位图的使用
2、位图实现
3、位图与哈希表的区别
4、位图的应用
结语
前言:
位图采用的是哈希表的思想,哈希表的映射层面是在字节上,而位图的映射层面就是在bit位上。由于bit位所能展现的信息无非只有‘1’和‘0’,所以位图相比于哈希表,前者的功能比较单调,只能判断数据存在与否,若数据存在则bit位置为‘1’,不存在则为‘0’,因此位图使用的场景也不允许有重复数据的出现。
1、位图的使用
举例,现有数组{1,4,5,9,6};则该数组在哈希表和位图下的映射关系如下图所示:
如上图所示,若想在位图中找到对应的映射位置,只需要对该数据进行‘/’‘%’的操作即可,比如:要计算数据9在第几个bit位上,则先9/8=1,表示在下标为1的元素上,再用9%8=1,表示在该元素的下标位1的bit位上(也就是9号bit位)。
2、位图实现
那么如何将位图中的bit位置为‘0’或‘1’呢,可以用一个数字1作为被操作对象,1的二进制序列的最低位是1,其余是0。比如上述的数据9的映射位置是在第二个元素的下标为1的bit位(也就是第二个bit),那么只需要把1左移一个bit位,则移动后1的二进制序列为000..00010(ps:当然此时二进制序列表示的不是1了),然后用该二进制序列直接与第二个元素相或(‘|’),这样就可以把该映射位置的bit位从‘0’变为‘1’了。
具体操作如下:
若要把位图中的bit置为0,可以对表达式“1<<j”进行取反操作,然后再与‘&’上映射位置即可。
位图代码实现如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <vector>
#include <iostream>
using namespace std;
template<size_t N>
class bitset
{
public:
bitset()
{
_bits.resize(N / 8 + 1, 0);//位图的大小和初始化
}
void set(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= (1 << j);//或-只要有一个为1结果就为1
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~(1 << j);//或-只要有一个为0结果就为0
}
bool find(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);//_bits[i]为1说明该数据存在
}
private:
vector<char> _bits;//位图其实是一个char类型的vector
};
int main()
{
bitset<100> bs;
bs.set(10);
bs.set(11);
bs.set(15);
cout << bs.find(10) << " ";
cout << bs.find(15) << endl;
bs.reset(10);
cout << bs.find(10) << " ";
cout << bs.find(15) << endl;
return 0;
}
运行结果:
3、位图与哈希表的区别
1、位图在bit为层面上操作,而哈希表在byte层面上操作。
2、每一个不重复的数据都会在位图中有唯一的位置(即位图不存在哈希冲突),而哈希表需要另外“挂桶"才可解决哈希冲突。
3、位图面对海量数据时比哈希表更节省空间,但是面对数据量小且特殊时,位图所消耗的空间可能比哈希表大,比如只有4个数据:1,10,100,100000,若用位图则必须开100000/8个char元素。
4、位图只能映射整形数据,哈希表可以映射多种类型的数据。
4、位图的应用
比如:给定一百亿个整数,要求查找该数据集中只出现一次的数据。
思路: 因为位图只能记录两种状态:存在/不存在,所以一张位图显然是很难完成这项要求的,因为无非判断出现2次以上的场景,因此这里需要两张位图。如果一个数据出现了一次,则第一个位图置为1,出现两次则第二个位图置为1,出现更多就不处理了。然后遍历这两种位图,若第一个位图是1,第二个位图是0,则表明该位置对应的数据只出现了一次。
代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <vector>
#include <iostream>
using namespace std;
template<size_t N>
class bitset
{
public:
bitset()
{
_bits.resize(N / 8 + 1, 0);//位图的大小和初始化
}
void set(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= (1 << j);//或-只要有一个为1结果就为1
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~(1 << j);//或-只要有一个为0结果就为0
}
bool find(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);//_bits[i]为1说明该数据存在
}
private:
vector<char> _bits;//位图其实是一个char类型的vector
};
template<size_t N>
class twobitset
{
public:
void set(size_t x)
{
//第一次出现 置为0 1
if (_bs1.find(x) == false
&& _bs2.find(x) == false)
{
_bs2.set(x);
}
//第二次出现 置为1 0
else if (_bs1.find(x) == false
&& _bs2.find(x) == true)
{
_bs1.set(x);
_bs2.reset(x);
}
//第三次出现不处理
}
void Print()
{
for (size_t i = 0; i <= N; ++i)
{
if (_bs2.find(i))
{
cout << i << endl;
}
}
}
public:
bitset<N> _bs1;
bitset<N> _bs2;
};
int main()
{
int a[] = { 6,6,6,3,2,1,7,9,9,7,2,10,12 };
twobitset<12> bs;
for (auto e : a)
{
bs.set(e);
}
bs.Print();
return 0;
}
运行结果:
结语
以上就是关于位图的讲解,位图实际上就是操作bit位的哈希表,实现起来也相对简单。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!