位图的介绍
位图的引入
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?
要判断一个数是否在某一堆数中,我们可能会想到如下方法:
· 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
· 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。
单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O ( N l o g N ) O(NlogN)O(NlogN),第二种方法的时间复杂度是O ( N ) O(N)O(N)。
但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。
位图解决
实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。比如:
无符号整数总共有232个,因此记录这些数字就需要232个比特位,也就是512M的内存空间,内存消耗大大减少。
位图的概念
位图 (Bitmap) 是一种基于位操作的数据结构,用于表示一组元素的集合信息。它通常是一个仅包含0和1的数组,其中每个元素对应集合中的一个元素。位图中的每个位(或者可以理解为数组的元素)代表一个元素是否存在于集合中。当元素存在时,对应位的值为1;不存在时,对应位的值为0。位图常用于判断某个元素是否属于某个集合,或者对多个集合做交集、并集或差集等集合运算。
可能这么多听起来很复杂,其实总结下来就这个意思:
位图本质是个数组,用来存放0和1。
位图通过自身数组中的每个位来代表集合(我们要处理的数据)中的元素,每个位是0或1,代表元素的存在与否(0,不存在;1,存在)。
位图的实现
主要包含三个核心接口:设置(设为1)、重置(设为0)、判断(是0还是1)。
对于每一步可以看注释
#include<iostream>
#include<vector>
using namespace std;
namespace yxh
{
//N代表数据范围
template<size_t N>
class bit_set
{
public:
bit_set()
{
_bits.resize(N / 8 + 1, 0);
}
void set(size_t x)
{
//由于一个组是char,所以x/8是计算在哪个char组里
size_t i = x / 8;
//一个char里有8位,x%8是计算出在char组里面的具体哪一位
size_t j = x % 8;
//这个建议大家画图理解,首先_bits[i]是对应的char组,然后1<<j,其实是将1向左移动了j位,然后再或等,这样就把对应位置上的数置为1了,可以画图理解.
_bits[i] |= (1 << j);
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
//也是先1向左移动j位,然后再按位取反,此时除了目标位是0,别的位都是1.然后再&上这个数,任何数&1都是它本身,&0都是0.所以此时将目标位设置为0了
_bits[i] &= ~(1 << j);
}
bool test(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
//任何数&1都是它本身,因为我们可以利用这一点来判断目标位是0还是1.
//注意此时不再需要加等于号了,因为这是判断,不是修改。
return _bits[i] & (1 << j);
}
private:
vector<char> _bits;
};
}
位图的使用
位图的应用
在实际应用中,位图可以用于各种用途,例如:
- 存在性检测:使用位图快速检测某个元素是否存在。
- 权限管理:用位图表示用户的权限,每个位代表一种权限。
- 内存管理:操作系统使用位图来管理内存分配,跟踪内存块的使用情况。
- 集合操作:用位图表示集合,实现集合的并、交、差运算。
位图的高级应用
位图可以进行更高级的操作,如按位与、按位或、按位异或等。以下示例展示了如何实现这些操作:
#include <iostream>
#include <vector>
#include <cstdint>
class Bitmap {
public:
Bitmap(size_t size) : bits((size + 7) / 8, 0) {}
void set(size_t pos) {
bits[pos / 8] |= (1 << (pos % 8));
}
void reset(size_t pos) {
bits[pos / 8] &= ~(1 << (pos % 8));
}
bool test(size_t pos) const {
return bits[pos / 8] & (1 << (pos % 8));
}
Bitmap operator&(const Bitmap& other) const {
Bitmap result(bits.size() * 8);
for (size_t i = 0; i < bits.size(); ++i) {
result.bits[i] = bits[i] & other.bits[i];
}
return result;
}
Bitmap operator|(const Bitmap& other) const {
Bitmap result(bits.size() * 8);
for (size_t i = 0; i < bits.size(); ++i) {
result.bits[i] = bits[i] | other.bits[i];
}
return result;
}
Bitmap operator^(const Bitmap& other) const {
Bitmap result(bits.size() * 8);
for (size_t i = 0; i < bits.size(); ++i) {
result.bits[i] = bits[i] ^ other.bits[i];
}
return result;
}
void print() const {
for (size_t i = 0; i < bits.size(); ++i) {
for (int j = 0; j < 8; ++j) {
std::cout << ((bits[i] & (1 << j)) ? '1' : '0');
}
}
std::cout << std::endl;
}
private:
std::vector<uint8_t> bits;
};
int main() {
Bitmap bitmap1(16);
Bitmap bitmap2(16);
bitmap1.set(1);
bitmap1.set(5);
bitmap1.set(8);
bitmap2.set(1);
bitmap2.set(4);
bitmap2.set(8);
Bitmap and_result = bitmap1 & bitmap2;
Bitmap or_result = bitmap1 | bitmap2;
Bitmap xor_result = bitmap1 ^ bitmap2;
std::cout << "Bitmap 1: ";
bitmap1.print(); // 0100010010000000
std::cout << "Bitmap 2: ";
bitmap2.print(); // 0100100010000000
std::cout << "AND result: ";
and_result.print(); // 0100000010000000
std::cout << "OR result: ";
or_result.print(); // 0100110010000000
std::cout << "XOR result: ";
xor_result.print(); // 0000110000000000
return 0;
}
bitset
位图(Bitmap)和 bitset
有许多相似之处,但它们并不完全相同。以下是两者的比较和具体区别:
bitset
(C++标准库中的位集)
bitset
是C++标准模板库中的一种容器,用于处理固定长度的二进制序列(位集)。它提供了高效的位操作,并且在编译时确定大小。
bitset
的特点:
- 固定长度:
bitset
的大小在编译时就确定,不能在运行时改变。 - 类型安全:作为STL的一部分,
bitset
提供类型安全和标准化的接口。 - 高效操作:
bitset
提供丰富的成员函数和操作符,使位操作更为简洁和安全。
bitset
的使用示例:
#include <bitset>
#include <iostream>
int main() {
std::bitset<16> bs;
bs.set(1);
bs.set(5);
bs.set(8);
std::cout << bs << std::endl; // 输出位集状态
bs.reset(5);
std::cout << bs << std::endl; // 输出位集状态
std::cout << "Bit at position 8: " << bs.test(8) << std::endl; // 检查位状态
return 0;
}
比较和总结
- 长度:位图的长度可以动态调整,而
bitset
的长度在编译时确定,不可改变。 - 存储方式:位图通常使用字节数组实现,可以直接操作内存中的比特位;
bitset
是一个模板类,封装了位操作,提供更高层次的接口。 - 效率:两者在位操作上效率相似,但
bitset
提供了更多的成员函数和操作符,使代码更简洁和安全。 - 应用场景:位图适用于需要动态调整长度或直接操作内存的场景,如操作系统内存管理、网络位图等。
bitset
适用于固定长度的位操作场景,如标志集合、布尔数组等。
综上所述,位图是一种通用的低级数据结构,可以灵活地适应各种长度和应用需求。而 bitset
是C++标准库中的一种固定长度位集容器,适用于需要固定长度和标准接口的场景。两者在概念上相似,但在具体实现和应用上有所不同。