位图/位域

1.位图介绍:

位图(Bitmap),也被叫做位向量(Bit Vector),是一种高效的数据结构。它以位为单位来存储数据,每一位仅能取 0 或 1 这两个值,分别代表两种不同的状态,比如存在或不存在、真或假等。在内存使用上非常节省,适合处理大规模数据。例如在处理海量数据时,要判断某个元素是否存在,使用位图可大大减少内存占用。其核心操作包括设置位(将某一位设为 1)、重置位(将某一位设为 0)和测试位(检查某一位是 0 还是 1)。通过简单的位运算,位图能够快速完成这些操作,在数据去重、查找、排序等场景中应用广泛。

2.位图应用:

  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

3.位图注意:位图只能处理整型类型!

4.位图的实现(三个核心点):

简单举个例子:把存储结构想象成 “页码本”

假设你有一本很特别的 “页码本”,这本本子每一页可以记录 32 个小信息(就像每一页有 32 个小格子)。_bits 数组就相当于这本 “页码本”,数组里的每个元素,就好比本子的每一页。

数字x=页号 (i)* 32 + 位号(j)

代码:

namespace hush
{
	template
	class bitset
	{
	public:
		bitset()
		{
			_bits.resize(N / 32 + 1, 0);
		}
		void set(size_t x)
		{
			//这是设1
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] |= (1 << j);
		}
		void reset(size_t x)
		{
			//这是设0
			size_t i = x / 32;
			size_t j = x % 32;
			_bits[i] &= ~(1 << j);
		}
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			return _bits[i] & (1 << j);
		}

		//把放进去的数还原出来!
		void findSetNumbers() {
			for (size_t i = 0; i < _bits.size(); ++i) 
			{
				unsigned int num = _bits[i];
				for (size_t j = 0; j < 32; ++j) 
				{
					if (num & (1 << j)) 
					{
						size_t pos = i * 32 + j;
						std::cout << pos << " ";
					}
				}
			}
		}

	private:
		vector _bits;
	};

需要注意的是:1.不管大小端机,“<<”左移是往高位移动!“>>”右移是往低位移动!

2.set函数里面是或“|=”;reset函数里面是与取反“&=~”;test函数里面返回的是“&”

3."|"是按位或运算符,其运算规则是只要两个操作数对应位中有一个为 1,结果的对应位就为 1,所以set是置1;&是按位与运算符,规则是只有两个操作数对应位都为 1,结果的对应位才为 1,否则为 0,所以reset是置0函数。

5.位图的应用:

这样就可以采用双位图方法进行一个标记,来实现:代码结合上面一部分和下面的

namespace hush
{
	//实现100亿个整数找出出现一次的数//位图的复用
	template
	class twobitset
	{
	public:
		//00 -> 01从未出现过的
		//01-> 10出现过一次的
		//10 不变,出现两次以上!
		void set(size_t x)
		{
			if (bs1.test(x) == false && bs2.test(x) == false)
			{
				bs2.set(x);
			}
			else if(bs1.test(x)==false&&bs2.test(x)==true)
			{
				bs1.set(x);
				bs2.reset(x);
			}
		}

		void printOnce()
		{
			for (size_t i = 0; i < N; i++)
			{
				if (bs1.test(i) == false && bs2.test(i) == true) 
				{
					cout << i << endl;

				}
				
			}
			cout << endl;
		}

	private:
		bitset bs1;
		bitset bs2;

	};
	//实现100亿个整数找出出现一次的数//位图的复用
	template
	class twobitset
	{
	public:
		//00 -> 01从未出现过的
		//01-> 10出现过一次的
		//10 不变,出现两次以上!
		void set(size_t x)
		{
			if (bs1.test(x) == false && bs2.test(x) == false)
			{
				bs2.set(x);
			}
			else if(bs1.test(x)==false&&bs2.test(x)==true)
			{
				bs1.set(x);
				bs2.reset(x);
			}
		}

		void printOnce()
		{
			for (size_t i = 0; i < N; i++)
			{
				if (bs1.test(i) == false && bs2.test(i) == true) 
				{
					cout << i << endl;

				}
				
			}
			cout << endl;
		}

	private:
		bitset bs1;
		bitset bs2;

	};
}

int main()
{/*
	int i = 0;
	i <<= 8;*/
	//正数是0~127 负数是-1~-128
	hush::twobitset<100> bs;
	/*bs.set(40);
	bs.set(39);*/
	int arr[] = { 1,2,3,1,2,9};
	for (auto e : arr)
	{
		bs.set(e);
	}
	bs.printOnce();
	//这to_string可以帮你打印出来你改了以后的所有位数!
	/*cout << bs.to_string() << endl;*/
	return 0;
}

假设我们要处理的数集是 {10, 10, 20},使用 twobitset 来找出只出现一次的数。

  • 初始时,bs1 和 bs2 的所有位都是 0。
  • 插入 10:
    • 第一次插入 10,bs1 和 bs2 中对应 10 的位都是 0,执行 bs2.set(10),状态变为 01。
    • 第二次插入 10,bs1 中对应 10 的位是 0,bs2 中对应 10 的位是 1,执行 bs1.set(10) 和 bs2.reset(10),状态变为 10。
  • 插入 20:
    • 第一次插入 20,bs1 和 bs2 中对应 20 的位都是 0,执行 bs2.set(20),状态变为 01。
  • 调用 printOnce 函数:

遍历所有数,发现只有 20 的状态是 01,所以输出 20。

6.位域的介绍:

位域(Bit - field)是 C/C++ 等编程语言中的一种数据结构特性,用于在一个字节或多字节的整型数据内,以位为单位来定义和使用数据成员。它允许开发者精确地指定每个成员所占用的二进制位数,从而更紧凑地存储数据,有效节省内存空间。比如在处理一些标志位集合、协议包头格式等场景中,位域能将多个相关的标志或小数据项整合在一个整型变量中,方便操作和管理。不过,位域的使用可能会带来一些移植性问题,因为不同编译器在位域的存储顺序和内存分配方式上可能存在差异。

7.下面是位域使用的一个简单案例:表示星期几

#include 
#include 

// 定义一个包含 3 位位域的结构体来表示星期几
struct Weekday {
    unsigned int day : 3;  // 使用 3 位位域

    // 构造函数,用于初始化星期几
    Weekday(unsigned int d) : day(d) {}

    // 成员函数,用于返回星期几的字符串表示
    std::string toString() const {
        switch (day) {
            case 0: return "Sunday";
            case 1: return "Monday";
            case 2: return "Tuesday";
            case 3: return "Wednesday";
            case 4: return "Thursday";
            case 5: return "Friday";
            case 6: return "Saturday";
            default: return "Invalid";
        }
    }
};

int main() {
    // 创建一个 Weekday 对象,表示星期二
    Weekday weekday(2);

    // 输出星期几的字符串表示
    std::cout << "The weekday is: " << weekday.toString() << std::endl;

    return 0;
}    

这里之所以用3是因为一共就7天,用二进制来表示的话刚好三位:使用 3 位二进制数,其取值范围是000(十进制 0)到111(十进制 7)。通过限定day位域宽度为 3,既保证了可以完整表示一周的天数,又在一定程度上优化了内存使用。

位域和位图都是采用 bit(位)为单位进行分配的。

你可能感兴趣的:(c++,笔记,开发语言,算法)