当前位置: 首页 > news >正文

C++进阶(七)--STL--bitset(位图)的介绍与基本功能模拟实现

在这里插入图片描述

文章目录

  • 引入
  • 1.位图的介绍
    • 1.1位图的概念
    • 1.2位图的应用
    • 1.3bitset的基本使用
      • bitset的定义方式
      • bitset成员函数的使用
  • 2.位图的基本模拟实现
    • 2.1基本结构
    • 2.2构造函数
    • 2.3set函数
    • 2.4reset
    • 2.5test
  • 3.位图考察题目
    • 3.1只出现⼀次的整数?
    • 3.2找到两个文件交集?
    • 3.3出现次数不超过2次的所有整数(出现1次和2次)?
  • 总结

引入

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

要判断一个数是否在某一堆数中,我们可能会想到如下方法:

  • 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
  • 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。
    单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O(NlogN),第二种方法的时间复杂度是O(N)。

但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。

1.位图的介绍

实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。

在这里插入图片描述
无符号整数总共有232个,因此记录这些数字就需要232个比特位,也就是512M的内存空间,内存消耗大大减少。

1.1位图的概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

1.2位图的应用

常见位图的应用如下:

1. 快速查找某个数据是否在一个集合中。
2. 排序。
3. 求两个集合的交集、并集等。
4. 操作系统中磁盘块标记。
5. 内核中信号标志位(信号屏蔽字和未决信号集)。

1.3bitset的基本使用

bitset的定义方式

方式一: 构造一个16位的位图,所有位都初始化为0。

bitset<16> bs1; //0000000000000000

方式二: 构造一个16位的位图,根据所给值初始化位图的前n位。

bitset<16> bs2(0xfa5); //0000111110100101

方式三: 构造一个16位的位图,根据字符串中的0/1序列初始化位图的前n位。

bitset<16> bs3(string("10111001")); //0000000010111001

bitset成员函数的使用

bitset中常用的成员函数如下:

成员函数功能
set设置指定位或所有位
reset清空指定位或所有位
flip反转指定位或所有位
test获取指定位的状态
count获取被设置位的个数
size获取可以容纳的位的个数
any如果有任何一个位被设置则返回true
none如果没有位被设置则返回true
all如果所有位都被设置则返回true

具体用法可以用的时候再查询文档 bitset使用文档

2.位图的基本模拟实现

2.1基本结构

template<size_t N>
class bitset
{
public:
    // 构造函数
	bitset();
	// x映射的位标记成1
	void set(size_t x);
	// x映射的位标记成0
	void reset(size_t x);
	// x映射的位是1返回真,0返回假
	bool test(size_t x);
private:
	vector<int> _bs;
};

2.2构造函数

在构造位图时,我们需要根据所给位数N,创建一个N位的位图,并且将该位图中的所有位都初始化为0。

一个整型有32个比特位,因此N个位的位图就需要用到N/32个整型,但是实际我们所需的整型个数是N/32+1,因为所给非类型模板参数N的值可能并不是32的整数倍。

例如,当N为40时,我们需要用到两个整型,即40/32+1=2。

在这里插入图片描述

bitset()
{
	// 扩容,不管能否整除都多开一个空间
	_bs.resize(N / 32 + 1);
}

2.3set函数

设置位图中指定的位的方法如下:

计算出该位位于第 i 个整数的第 j 个比特位。
将1左移 j 位后与第 i 个整数进行或运算即可。
在这里插入图片描述

// x映射的位标记成1
void set(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
 
	_bs[i] |= (1 << j);
}

2.4reset

清空位图中指定的位的方法如下:

计算出该位位于第 i 个整数的第 j 个比特位。
将1左移 j 位再整体反转后与第 i 个整数进行与运算即可。
在这里插入图片描述

// x映射的位标记成0
void reset(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
 
	_bs[i] &= (~(1 << j));
}

2.5test

获取位图中指定的位的状态的方法如下:

计算出该位位于第 i 个整数的第 j 个比特位。
将1左移 j 位后与第 i 个整数进行与运算得出结果。
若结果非0,则该位被设置,否则该位未被设置。

在这里插入图片描述

// x映射的位是1返回真,0返回假
bool test(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;
 
	return _bs[i] & (1 << j);
}

3.位图考察题目

  • 给定100亿个整数,设计算法找到只出现⼀次的整数?
    虽然是100亿个数,但是还是按范围开空间,所以还是开2^32个位,跟前面的题目是⼀样的。
  • 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
    把数据读出来,分别放到两个位图,依次遍历,同时在两个位图的值就是交集
  • ⼀个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数 ?

根据上面的题意,我们需要通过位图实现一个能表示出现0次,1次,2次,多次的位图,因此可以实现一个两个位图成员的类。

namespace twobt
{
	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);
 
			if (!bit1 && !bit2) // 00->01
			{
				_bs2.set(x);
			}
			else if (!bit1 && bit2) // 01->10
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (bit1 && !bit2) // 10->11
			{
				_bs1.set(x);
				_bs2.set(x);
			}
		}
 
		// 返回0 出现0次数
		// 返回1 出现1次数
		// 返回2 出现2次数
		// 返回3 出现2次及以上
		int get_count(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);
 
			if (!bit1 && !bit2)
			{
				return 0;
			}
			else if (!bit1 && bit2)
			{
				return 1;
			}
			else if (bit1 && !bit2)
			{
				return 2;
			}
			else
			{
				return 3;
			}
		}
 
	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
}

3.1只出现⼀次的整数?

代码演示:

void test_twobitset1()
{
	lin::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}
	// 只出现一次的整数
	for (size_t i = 0; i < 100; ++i)
	{
		if (tbs.get_count(i) == 1)
		{
			cout << i << endl;
		}
	}
}

3.2找到两个文件交集?

代码演示

void test_bitset()
{
	int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
	int a2[] = { 5,3,5,99,6,99,33,66 };
 
	bitset<100> bs1;
	bitset<100> bs2;
 
	for (auto e : a1)
	{
		bs1.set(e);
	}
 
	for (auto e : a2)
	{
		bs2.set(e);
	}
	// 两个文件的交集
	for (size_t i = 0; i < 100; i++)
	{
		// 第i的位置都是1就是交集
		if (bs1.test(i) && bs2.test(i))
		{
			cout << i << endl;
		}
	}
}

3.3出现次数不超过2次的所有整数(出现1次和2次)?

代码演示

void test_twobitset2()
{
	lin::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}
	// 出现次数不超过2次的所有整数
	for (size_t i = 0; i < 100; ++i)
	{
		//cout << i << "->" << tbs.get_count(i) << endl;
		if (tbs.get_count(i) == 1 || tbs.get_count(i) == 2)
		{
			cout << i << endl;
		}
	}
}

总结

位图的优缺点:
优点:增删查改快,节省空间
缺点:只适用于整形


本篇博客到此结束,欢迎各位评论区留言~

相关文章:

  • PyCharm环境配置实战:从搭建到优化的完整指南
  • 观察者模式的C++实现示例
  • Qt:事件
  • 【MySQL】用MySQL二进制包构建docker镜像
  • 基于javaweb的SpringBoot田径运动会管理系统设计和实现(源码+文档+部署讲解)
  • 天津大学02-深度解读DeepSeek:部署、使用、安全【文末附下载链接】
  • leetcode:2965. 找出缺失和重复的数字(python3解法)
  • c盘爆红后,使用diskgenius给C盘无损扩容,操作记录
  • LeetCode Hot100刷题——两数相加(链表)
  • Minix OS的配置 SSH C程序编译
  • git 中 commit 的修改
  • Redis 篇
  • java之http传MultipartFile文件
  • QT day4
  • Kolla-Ansible deploy部署报错 KeyError: \\‘KernelMemory
  • 【Leetcode 每日一题】1328. 破坏回文串
  • 数据结构知识学习小结
  • 常用的分布式ID设计方案
  • golang debug调试
  • 集成的背景与LLM集成学习
  • 珠海关键词优化收费/厦门谷歌seo公司
  • 购物网站首页界面设计/免费论坛建站系统
  • 电影网站建设教学视频/西安seo排名扣费
  • 服务器网站建设维护合同/花西子网络营销案例分析
  • 做upc条形码的网站/海外推广营销平台
  • 济南网站建设 贯日/外链生成网站