位图bitset的模拟实现与位运算

位图bitset与位运算

前言

​ 本文将探讨C++中的位操作,可以用来进行高效的数据处理和存储。通过介绍bitsettwo_bitset类模板,我们将展示如何利用C++的位操作来解决实际问题,从而提高代码的性能和可读性。


一、基本的位运算

在C++中,位运算是整数类型数据在位级别上进行操作的运算符。这些运算符包括:

  • 位与(AND)运算符 :&

当两个操作数对应的位都为1时,结果位为1,否则为0。

// 位与(AND)运算符 &
int a = 5; // 二进制表示: 0101
int b = 9; // 二进制表示: 1001
int c = a & b; // 结果为 1 (0001)

在这里插入图片描述

  • 位或(OR)运算符 :|

当两个操作数对应的位中至少有一个为1时,结果位为1,否则为0。

// 位或(OR)运算符 |
c = a | b; // 结果为 13 (1101)

在这里插入图片描述

  • 位异或(XOR)运算符:^

当两个操作数对应的位只有一个为1时,结果位为1,如果两个都为0或都为1,则结果位为0。

// 位异或(XOR)运算符 ^
c = a ^ b; // 结果为 12 (1100)

在这里插入图片描述

  • 位非(NOT)运算:~

对操作数的每一位进行取反操作,即1变0,0变1。

// 位非(NOT)运算符 ~
c = ~a; // 结果为 -6 (取决于系统的整数表示方法)

在这里插入图片描述

在这里插入图片描述

  • 左移(Left Shift)运算符:<<

将操作数的所有位向左移动指定的位数,右边空出的位用0填充。

// 左移(Left Shift)运算符 <<
c = a << 1; // 结果为 10 (1010)

在这里插入图片描述

  • 右移(Right Shift)运算符:>>

将操作数的所有位向右移动指定的位数,左边空出的位用0或符号位填充(取决于数据类型)。

// 右移(Right Shift)运算符 >>
c = a >> 1; // 结果为 2 (0010)

在这里插入图片描述

二、位图 (bitset)

bitset 类模板框架

template<size_t N>
	class bitset
	{
		typedef bitset<N> Self;
	public:
		bitset();
	public:
		Self& set(size_t pos);	// 将pos值所在比特位设为1
		
		Self& reset(size_t pos);		// 将pos值所在比特位清零

		bool test(size_t pos);	// 判断pos值是否在bitset中

	private:
		std::vector<int> _bits;
	};

私有成员变量 _bits

_bits是一个整数向量,用于存储位集合的实际位值。

private:
    std::vector<int> _bits;

构造函数

bitset()
{
	_bits.resize(N/32 + 1, 0);
}

由于 int 类型占 4 字节,1字节占 8 bit ,所以 vector 容器 _bits 需要设置的大小为 N/(4*8) ,但是由于C++中正数如果不被整除会存在向下取整的问题,所以最好在设定空间时在原基础上 +1。

set 成员函数

set函数将指定位置的位设置为1。

Self& set(size_t pos)
{
    assert(pos <= N);
    size_t i = pos / 32;
    size_t j = pos % 32;
    _bits.at(i) |= (1 << j);
    return *this;
}

reset 成员函数

reset函数将指定位置的位重置为0。

Self& reset(size_t pos)
{
    assert(pos <= N);
    size_t i = pos / 32;
    size_t j = pos % 32;
    _bits.at(i) &= ~(1 << j);
    return *this;
}

test 成员函数

test函数检查指定位置的位是否为1。

bool test(size_t pos)
{
    assert(pos <= N);
    size_t i = pos / 32;
    size_t j = pos % 32;
    return _bits.at(i) & (1 << j);
}

bitset 常用场景

当给定数亿级别个的整数,另问快速判断某个数是否在给定数集中?

// 快速判断一个整数是否在数组中
void test1()
{
	bit_set::bitset<100> bs;
	int arr[] = { 2, 6, 3, 4, 7, 6, 8, 3, 6, 1, 6 };
	for (auto e : arr)
	{
		bs.set(e);
	}

	for (int i = 0; i < 10; i++)
	{
		if (bs.test(i))
		{
			cout << i << " -> " << "存在" << endl;
		}
		else
		{
			cout << i << " -> " << "不存在" << endl;
		}
	}
	cout << "***************************" << endl;

	bs.reset(3);
	bs.reset(4);

	for (int i = 0; i < 10; i++)
	{
		if (bs.test(i))
		{
			cout << i << " -> " << "存在" << endl;
		}
		else
		{
			cout << i << " -> " << "不存在" << endl;
		}
	}
}

给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

void test3()
{
	int arr[] = { 2, 6, 3, 4, 7, 6, 8, 3, 6, 1, 6 };
	int arr2[] = { 9, 1, 6, 5, 3, 3 };

	bit_set::bitset<10> bs1;
	bit_set::bitset<10> bs2;
	for (auto e : arr)
	{
		bs1.set(e);
	}
	for (auto e : arr2)
	{
		bs2.set(e);
	}

	for (int i = 0; i < 10; i++)
	{
		if (bs1.test(i) && bs2.test(i))
		{
			cout << i << endl;
		}
	}
}

三、位图衍生 (two_bitset)

two_bitset 类模板框架

two_bitset类模板是bitset的扩展,它使用两个bitset来跟踪每个位出现的次数。

template<size_t N>
class two_bitset
{
public:
	two_bitset& set(size_t pos);

	bool test_appear_not_upOf2(size_t pos);
private:
	bitset<N> bs1;
	bitset<N> bs2;
};

私有成员变量 _bs1, _bs2

bs1bs2是两个bitset实例,用于跟踪每个位的出现次数。

private:
	bitset<N> bs1;
	bitset<N> bs2;

构造函数

由于成员变量 bs1, bs2 都是自定义类型,所以类在调用默认构造函数时会回调自定义成员变量的构造函数,故在此使用默认构造函数。

set 成员函数

set函数用于增加位的出现次数。

two_bitset& set(size_t pos)
{
	// 00 表示未出现
	// 01 表示出现一次
	// 10 表示出现两次
	// 11 表示出现三次及以上

	// 00->01 => bs1:0  bs2:1
	if (bs1.test(pos) == false && bs2.test(pos) == false)
	{
		bs2.set(pos);
	}
	else if (bs1.test(pos) == false && bs2.test(pos) == true)	// 01->10 => bs1:1  bs2:0
	{
		bs1.set(pos);
		bs2.reset(pos);
	}
	else if (bs1.test(pos) == true && bs2.test(pos) == false)	// 10->11 => bs1:1  bs2:1
	{
		bs2.set(pos);
	}
	return *this;
}

test 成员函数

test 类型函数为了适应不同的场景而存在,完全允许通过特定场景设计特定的 test 函数。所以这里暂且搁置,先看场景,再根据需求回头构建我们的 test 函数。

two_bitset 应用场景

位图应用变形:

1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

  • 此时根据场景我们即可设定属于我们的 test 函数

test_appear_not_upOf2函数用于检查位的出现次数是否不超过2次。

bool test_appear_not_upOf2(size_t pos)
{
	if (bs1.test(pos) == true && bs2.test(pos) == true)
	{
		return false;
	}
	return true;	// 出现过两次及两次以下
}

自此,我们即可利用自己实现的 set 函数和 test 函数对该问题进行求解。

示例代码:

// 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
void test2()
{
	bit_set::two_bitset<10> tbs;
	int arr[] = { 2, 6, 3, 4, 7, 6, 8, 3, 6, 1, 7, 3, 7 };
	for (auto e : arr)
	{
		tbs.set(e);
	}

	for (int i = 0; i < 10; i++)
	{
		if (tbs.test_appear_not_upOf2(i))
		{
			cout << i  << endl;
		}
		else
		{
			cout << i << " -> occured less than 2" << endl;
		}
	}
}

总结

​ 通过本文的介绍,我们可以看到位操作在C++中的强大之处。bitsettwo_bitset类模板为处理大量数据提供了一种高效且直观的方法。无论是在内存受限的环境中处理大规模数据集,还是在日常编程中寻找优化的机会,位操作都是一个不可或缺的工具。

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

螺蛳粉只吃炸蛋的走风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值