C++——位图的介绍和使用

位图的介绍

位图的引入

给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;
	};
}

位图的使用

位图的应用

在实际应用中,位图可以用于各种用途,例如:

  1. 存在性检测:使用位图快速检测某个元素是否存在。
  2. 权限管理:用位图表示用户的权限,每个位代表一种权限。
  3. 内存管理:操作系统使用位图来管理内存分配,跟踪内存块的使用情况。
  4. 集合操作:用位图表示集合,实现集合的并、交、差运算。

位图的高级应用

位图可以进行更高级的操作,如按位与、按位或、按位异或等。以下示例展示了如何实现这些操作: 

#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 的特点:

  1. 固定长度bitset 的大小在编译时就确定,不能在运行时改变。
  2. 类型安全:作为STL的一部分,bitset 提供类型安全和标准化的接口。
  3. 高效操作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;
}

比较和总结

  1. 长度:位图的长度可以动态调整,而 bitset 的长度在编译时确定,不可改变。
  2. 存储方式:位图通常使用字节数组实现,可以直接操作内存中的比特位; bitset 是一个模板类,封装了位操作,提供更高层次的接口。
  3. 效率:两者在位操作上效率相似,但 bitset 提供了更多的成员函数和操作符,使代码更简洁和安全。
  4. 应用场景:位图适用于需要动态调整长度或直接操作内存的场景,如操作系统内存管理、网络位图等。 bitset 适用于固定长度的位操作场景,如标志集合、布尔数组等。

综上所述,位图是一种通用的低级数据结构,可以灵活地适应各种长度和应用需求。而 bitset 是C++标准库中的一种固定长度位集容器,适用于需要固定长度和标准接口的场景。两者在概念上相似,但在具体实现和应用上有所不同。 

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值