C++STL详解(十一)-- 位图(bitset)

这篇具有很好参考价值的文章主要介绍了C++STL详解(十一)-- 位图(bitset)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

位图的介绍

位图的引入

有一道面试题:

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

对于这道题,我们有两个思路:
内存内查找: 面对40亿个无符号整数,我们可以使用搜索树和哈希表,时间复杂度也就为O(1),因为搜索树不仅存储数据,还要存储颜色,parent,child指针等,哈希表还要存储迭代器,size等内置成员,进而导致内存存不下.

文件内查找:排序 + 二分查找,时间复杂度为0(1),但是数据太大,只能放在文件上,但是磁盘运行效率太低,不好支持二分查找).

综合以上情况,我们可以采取位图解决:
位图不像搜索树和哈希表那样需要存储数据,数据是否在所给数据中,只有两种状态,在或者不在,那么可以使用一个二进制比特位来代表数据是否存在,二进制比特位为1,代表存在,为0,代表不存在,并且使用直接定址法来确定数据映射位置.
例如以下图示:
C++STL详解(十一)-- 位图(bitset)
位图的大小判断:
在本题中,40亿的无符号整型的范围为:0–4294967295,在开辟位图空间时,我们不是根据数据的个数在位图上映射的,而是根据数据的大小映射在位图上.所以,我们要开2^32-1的比特位大小的空间,让所有无符号整型数据都能映射在位图上.

那么2^32-1个比特位要占用多少空间呢?

图示如下:
C++STL详解(十一)-- 位图(bitset)

位图的概念

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

位图的应用

1 : 快速查找某个数据打是否在一个集合中.

2: 排序 + 去重 . ( 根据位图性质,哈希函数映射原理)

3: 求两个集合的交集,并集等.

4: 操作系统磁块标记.

位图的使用

位图的定义

方式一: 构造一个8位的位图,所有位默认初始化为0.

bitset<8> bs; //00000000

方式二: 构造一个8位的位图,使用string类型对象初始化.

bitset<8> bs( string("1111" ))  // 00001111

方式三: 构造一个8位的位图,使用字符串"1111"初始化.

bit<8> bs("1111");  //00001111

位图的成员函数

成员函数 作用
set 设置指定位或所有位(状态设为1 )
reset 清空指定位或所有位(状态设为0)
test 获取指定位的状态
flip 反转指定位或者所有位
count 获取被设置位的个数
size 获取位图中可以容纳状态位的个数
any 查看位图所有状态位中是否有1
none 查看位图中状态位是否都为空
all 查看位图中状态位是否都为1
#include <bitset>
int main()
{
	bitset<8> bs("1110");
	
	bs.set(0);                 //设置指定位

	cout << bs << endl;        //00001111

	bs.reset(0);               //清空指定位

	cout << bs << endl;        //00001110

	bs.flip(0);               //反转指定位

	cout << bs << endl;       //00001111

	cout << bs.none() << endl;  //0 

	cout << bs.any() << endl;  //1

	bs.set();                  //将位图所有位设置为1

	cout << bs.all() << endl;       //1
}

注意:
flip,set,reset等成员函数如果未设置指定位时,则默认作用于位图中的全部数据
如果设置指定位,则作用于指定位.

位图运算符的使用

位图中针对运算符进行了重载,我们可以直接在位图中使用.

#include <bitset>
int main()
{     
	bitset<8> bs;      //00000000
	
	//输入运算符
	cin >>  bs;        //1111
    //输出运算符
	cout << bs << endl; //00001111         
   
    bitset<8> bs1("1110");
    
    bitset<8> bs2("1100");
    
    //位运算符
    cout << ( bs1 & bs2) << endl; // 0000 1100
     
    cout << ( bs1 | bs2 ) << endl; // 0000 1110

    cout <<  bs1 ^ bs2 ) << endl;  // 0000 0010
    
    //[]运算符
    bs1[0] = 1;
     
   cout << bs1 << endl;          //0000 1111;

    return 0;
}

位图的模拟实现

成员函数

构造函数

我们开辟内存时,一般是以char类型(1个字节)开辟的,如果有N个数据,那么就要需要映射到N个比特位.此时计算时开辟空间时有两种情况:
如果 N / 8整除,那么我们直接根据结果开辟字节空间.
如果N / 8 不被整除,那么剩下的数据就没有比特映射了.

综合以上两种情况:
我们采用不过整没整除,我们都比计算值多开辟一个字节空间.

 bit_set()
		{
			_bit.resize(N / 8 + 1);
		}

set reset test

ret成员函数作用主要是将x映射的状态位标为1,

其主要有三个步骤:
(1): 计算数据x在第i个char类型大小的字节空间内.

(2): 计算数据x在第i个char类型空间的第j个位中.

(3); 将1左移j位与第i个char类型进行或等运算.
示图如下:
C++STL详解(十一)-- 位图(bitset)

reset成员函数的作用是将x的映射的状态位标为0

其主要有三个步骤:
(1): 计算数据x在第i个char类型大小的字节空间内.

(2): 计算数据x在第i个char类型空间的第j个位中.

(3): 将1左移j位取反后再与第i个类型进行与等运算.
图示如下:
C++STL详解(十一)-- 位图(bitset)

test成员函数的作用是检测x映射的状态位的状态

如果第j位的状态为 0, 那么经过与运算的结果就为0,转换为bool就表示false.

如果第j位的状态位 1, 那么经过与运算的结果为1,转换为bool表示true.

C++STL详解(十一)-- 位图(bitset)

代码如下:

void set(size_t x )
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bit[i] &= ~(1 << j);
		}

flip,size,count

flip成员函数用于反转比特位.

filp成员函数步骤如下:
1: 计算该位位于第i个char类型的第j个比特位.

2: 将1左移j位后再与第i个char类型异或运算.
C++STL详解(十一)-- 位图(bitset)

void flip ( size_t x )
{
   size_t i =  x / 8;
   size_t j =  x % 8;
   _bit[i] ^= ( 1 << j );
}

size成员函数用于获取位图中可以容纳的位的个数

直接返回非类型模板参数就代表了数据的个数,也就代表了位图的大小.

size_t size()
{
   return N;
}

count成员用于获取被设置位的个数

我们知道,获取位图中被设置的位的个数,也就是统计中位图中状态位为1的个数.
步骤如下:
1: 遍历位图,取类型为char大小的比特位n和n-1进行与运算进而得到新的n.(每次计算都使位图状态位为1的个数-1.)

2: 判断n是否为0,如果不为0则继续循环(循环的次数,就代表该char类型比特位为1的个数)

3: 当位图遍历完,每个char类型的比特位循环的总次数就代表了该位图状态为1的总个数.

size_t count()
		{
			size_t count = 0;

			for ( auto e: _bits)
			{
				char x = e;
				while ( x )
				{
					 x = x & (x - 1);
					count++;
				}
			}
			return count;
		}
			
			

none,any,all

none查看位图中是否状态位都为空

在位图中以char类型大小的比特位进行遍历,查看是否为0.

bool none()
{
    for ( auto e : _bit )
    {
         if( e != 0 )
         {
            return false;}
    return true;
}

any函数查看位图中的状态位是否存在1.

bool any()
{
   return !none();
}

这里是引用all函数查看位图中的状态位是否都为1.

由于我们在构造位图时始终多开了一个char类型大小(8比特位)的空间,且这最后8比特位中只N%8个比特位是有效的,剩下的空间是没有数据映射的,是无效的,此时有两种情况:
步骤如下:
1:检查数据N所占实际的char个数空间大小,即N / 8.

2: 检查最后一个char中有效的比特位是否位1.

C++STL详解(十一)-- 位图(bitset)

bool all()
		{
			size_t size = _bits.size();

			for (size_t i = 0; i < N / 8; i++)
			{
				if (~_bits[i] != 0)//取反应该为0,否则取反之前不全为1,返回false
					return false;
			}
	
			for (size_t j = 0; j < N % 8; j++) 	//再检查最后一个char的前 N%8 个位
			{
				if ((_bits[ size- 1 ] & (1 << j)) == 0)//最后一个char有多少j个1就循环j次.
					return false;
			}
			return true;
		}

位图应用题扩展

题目一:

给定100亿个整数,设计算法找到只出现一次的整数?

我们知道1个位图可以表示两种状态,那么两个位图可以表示四种状态,针对该题,我们可以设计以下三种状态:

C++STL详解(十一)-- 位图(bitset)
为了减少哈希映射位置的计算,我们可以采取复用位图的方式,设计出包含两个位图的类.

其中成员函数set的作用及步骤如下:
如果x映射位置的状态位为00, 则调用位图2的set,进而实现状态位为01.

如果x映射位置的状态位为01,则调用位图1的set,位图2的reset,进而实现总状态位为10.

图示如下:
C++STL详解(十一)-- 位图(bitset)
代码如下:

	template < size_t N >
	class twobit_set
	{
	public:
		void set(size_t x)                            
		{
			bool inset1 = _bs1.test(x);
			bool inset2 = _bs2.test(x);
			if ( inset1 == false && inset2 == false )  //如果状态为 00
			{
				_bs2.set(x);                           //设计为01;
			}
			else if (inset1 == false && inset2 == true) //如果状态为 01
			{
				_bs1.set(x);                               
				_bs2.reset(x);                         //设计为10
			}
		}
		void print_once_num()                       //遍历比特位,将数据在位图的状态位为01的数据打印.
		{
			for ( size_t i = 0; i < N; ++i )
			{
				if (_bs1.test(i) == false && _bs2.test(i) == true)
				{
					cout << i << " ";
				}
			}
	    }
		
	private:
		bit_set<N> _bs1;
		bit_set<N> _bs2;
	};

代码如下:

题目二:

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

题解如下:
C++STL详解(十一)-- 位图(bitset)文章来源地址https://www.toymoban.com/news/detail-460666.html

template < size_t N >
	class twobit_set
	{
	public:
		void set(size_t x)                            // 00 变 01  
		{
			bool inset1 = _bs1.test(x);
			bool inset2 = _bs2.test(x);
			if ( inset1 == false && inset2 == false )
			{
				_bs2.set(x);
			}
			else if (inset1 == false && inset2 == true) // 01 变 10  
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (inset1 == true && inset2 == false)       // 10 变 11;
			{
				_bs1.set(x);
				_bs2.set(x);
			}
		}
		void print_once_num()    
		{
			for ( size_t i = 0; i < N; ++i )
			{
				if (_bs1.test(i) == true && _bs2.test(i) == true)    //将状态为11的数据打印.
				{
					cout << i << " ";
				}
			}
	    }
		
	private:
		bit_set<N> _bs1;
		bit_set<N> _bs2;
	};

位图模拟实现代码

using namespace std;
namespace myBit
{
	template< size_t N >
	class bit_set
	{
	public:
		bit_set()
		{
			_bits.resize(N / 8 + 1, 0);
		}
		void flip(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] ^= (1 << j);
		}
		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_bits[i] |= (1 << j);
		}

		void reset(size_t x)
		{
			size_t i = x / 8;      //计算在位图的第几个char.

			size_t j = x % 8;      //计算在char中的第几个位置.

			_bits[i] &= ~(1 << j);  //只改变第j位的比特位,其他位没有改变.
		}
		//统计set中1的个数
		
		size_t count()
		{
			int count = 0;
		for (size_t i = 0; i <=  N / 8; ++i)
		{
			char  n = _bits[i];
			while (n)
			{
				n = n & (n - 1);
				count++;
			}
		}
		return count;
	}
			
		//	for (auto e : _bits)
		//	{
		//	    char  n = e;             //
		//		while (n)
		//		{
		//			n = n & (n - 1);
		//			count++;
		//		}
		//	}
		//	return count;
		//}
		size_t size()
		{
			return N;
		}
		bool test(size_t x)
		{
			size_t i = x / 8;

			size_t j = x % 8;

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

		}
		bool all()
		{
			size_t size = N / 8;

			for (size_t i = 0; i < N / 8; i++)
			{
				if (~_bits[i] != 0)//取反应该为0,否则取反之前不全为1,返回false
					return false;
			}
			//再检查最后一个char的前 N%8 个位
			for (size_t j = 0; j < N % 8; j++)
			{
				if ((_bits[ N - 1 ] & (1 << j)) == 0)//和test的原理一致
					return false;
			}
			return true;
		}
		bool none()
		{
			for (auto e : _bits)
			{
				if (e != 0)
				{
					return false;
				}
			}
			return true;
		}
		bool any()
		{
			return !none();
		}
		
	private:
		vector<char> _bits;
	
	};

	template < size_t N >
	class twobit_set
	{
	public:
		void set(size_t x)                            // 1:00 变 01 2: 01 变 10  
		{
			bool inset1 = _bs1.test(x);
			bool inset2 = _bs2.test(x);
			if ( inset1 == false && inset2 == false )
			{
				_bs2.set(x);
			}
			else if (inset1 == false && inset2 == true)
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (inset1 == true && inset2 == false)       // 10 变 11;
			{
				_bs1.set(x);
				_bs2.set(x);
			}
		}
		void print_once_num()
		{
			for ( size_t i = 0; i < N; ++i )
			{
				if (_bs1.test(i) == false && _bs2.test(i) == true)
				{
					cout << i << " ";
				}
			}
	    }
		
	private:
		bit_set<N> _bs1;
		bit_set<N> _bs2;
	};
	void test_set()                                 //测试代码
	{

		bit_set<9> bit_set;
		bit_set.set(0);
		bit_set.set(1);
		bit_set.set(2);

		bit_set.set(3);
		bit_set.set(4);
		bit_set.set(5);

		bit_set.set(6);
		bit_set.set(7);

		bit_set.set(8);

		bit_set.flip(0);

		cout << bit_set.none() << endl;
		cout << bit_set.count() << endl;
	}


}

到了这里,关于C++STL详解(十一)-- 位图(bitset)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++初阶】STL详解(五)List的介绍与使用

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 1.list是一种可以在常数范围内在任意位置进行插

    2024年02月04日
    浏览(58)
  • C++STL详解(一)一一string类介绍和使用

    一、string的定义方式 STL中string类实现了多个构造函数的重载,常用六个构造如下: 使用示例:使用前要包含string头文件 运行结果: **注意nops是一个无符号的-1转换成有符号是32个比特位全1大小实际是2^32次方约等于4个G,所以我们不可能定义那么长的字符串,所以len缺省值给

    2024年02月22日
    浏览(39)
  • 【C++初阶】C++STL详解(三)—— vector的介绍及使用

    ​ ​📝个人主页:@Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C++学习 🎯 长路漫漫浩浩,万事皆有期待 上一篇博客:【C++】string类常见题目详解(二) —— 把字符串转换成整数、反转字符串、反转字符串 II、反转字符串中的单词 III、字符

    2024年02月11日
    浏览(48)
  • 【C++】STL——容器适配器priority_queue(优先级队列)详解 及 仿函数的介绍和使用

    这篇文章我们接着上一篇的内容,再来学一个STL里的容器适配器—— priority_queue (优先级队列) 1.1 priority_queue的介绍 我们上一篇文章学了 queue (队列),那优先级队列也是在 queue 里面的: 和 queue 一样, priority_queue 也是一个容器适配器,那他和 queue 有什么区别呢?我们一

    2024年02月07日
    浏览(48)
  • Dubbo引入Zookeeper等注册中心简介以及DubboAdmin简要介绍,为后续详解Dubbo各种注册中心做铺垫!

    文章目录 一:Dubbo注册中心引言 1:什么是Dubbo的注册中心? 2:注册中心关系图解 3:引入注册中心服务执行流程 4:Dubbo注册中心好处 5:注册中心核心作用 二:注册中心实现方案 1:早期 2:当前现状 三:DubboAdmin介绍 1:DubboAdmin简介 2:DubboAdmin的主要功能         Dubbo注册

    2024年02月05日
    浏览(46)
  • 位图的详解

    目录 位图 位图的概念 位图的实现 位图常见三道面试题 1.给定100亿个整数,设计算法找到只出现一次的整数? 2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?  3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的

    2024年02月12日
    浏览(31)
  • bmp位图格式详细介绍-1/4/8/16/24/32bit、存储格式等

    目录 一、概述 二、 .bmp 格式文件详解  2.1 位图文件头  2.2 位图信息头  2.3 调色板  2.4 位图数据 三、位图的其他知识  3.1 压缩的位图 bmp 是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,随着windows的流行, bmp 位图格式被广泛应用。 位图按照 是

    2023年04月18日
    浏览(52)
  • 大数据Flink(五十一):Flink的引入和Flink的简介

    文章目录 Flink的引入和Flink的简介 一、Flink的引入 1、第1代——Hadoop MapReduce

    2024年02月15日
    浏览(46)
  • [STL]list使用介绍

    注:本文测试环境是visual studio2019。 list是可以在常量时间内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。 list与

    2024年02月15日
    浏览(56)
  • Golang bitset 基本使用

    安装: 下面代码把fmtx换成fmt就行 布隆过滤器-假阳性率计算公式 可知在哈希函数的个数k一定的情况下: 位数组长度m越大,假阳性率越低; 已插入元素的个数n越大,假阳性率越高。 把fmtx删掉就行 目录格式 使用

    2024年02月13日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包