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

【C++】-- STL之位图

目录

一、位图概念

1.使用场景 

2.位图

二、位图介绍

1.构造

2.operator[ ]

3.count( )

4.size( )

5.test( )

6.any( )

7.none( )

8.all( )

9.set( )

10.reset( )

11.flip( )

三、位图实现

1.位图定义

2.构造

3.标记

4.删除

5.检验在不在

6.完整代码段

四、位图应用


一、位图概念

1.使用场景 

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

计算一下40亿个无符号整数会占多大内存呢?

可以采用如下方法:

(1)放进set或unordered_set中,再用find进行查找

(2)排序,再使用二分法查找,排序的时间复杂度是O(N*logN),二分查找的时间复杂度是O(logN)

但是,16GB的大小,这会很消耗内存,我们不可能把16GB的数据全部加载到内存中。还有另外一种比较节省内存的方式——位图。

2.位图

        位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
        数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:

一个bit位代表一个整数在不在,因此现在一个整数大小(4个字节32位)可以表示32个整数在不在,那么对于第一小节的40亿个整数16GB就可以用500MB进行表示了,500MB是可以加载到内存中的,这也提高了效率。

二、位图介绍

1.构造

 有3种构造方式:分为默认0、整数、字符串3种类型

bitset();//默认
bitset (unsigned long val);//参数传整数,10进制或16进制都可以
template<class charT, class traits, class Alloc>  explicit bitset (const basic_string<charT,traits,Alloc>& str,    typename basic_string<charT,traits,Alloc>::size_type pos = 0,    typename basic_string<charT,traits,Alloc>::size_type n =      basic_string<charT,traits,Alloc>::npos);//字符串

用3种构造方式构造3个位图对象:

#include<iostream>
#include<string>
#include<bitset>
using namespace std;

int main()
{
	bitset<16> bs1;
	bitset<16> bs2(125);
	bitset<32> bs3("101000101010");

	cout << "bs1:" << bs1 << endl;
	cout << "bs2:" << bs2 << endl;
	cout << "bs3:" << bs3 << endl;

	return 0;
}

打印结果如下: 

2.operator[ ]

返回下标位置的值: 

bool operator[] (size_t pos) const;reference operator[] (size_t pos);

 修改bs1第3位和第6位的值:

	bs1[3] = 1;
	bs1[6] = 1;
	cout << "bs1:" << bs1 << endl;

3.count( )

返回位图对象中1的个数:

size_t count() const;

 返回bs3中1的个数:

    cout << bs3.count() << endl;

4.size( )

返回该位图对象的位数: 

size_t size() const;

 返回bs1、bs2、bs3的位数:

	cout << "bs1:" << bs1.size() << endl;
	cout << "bs2:" << bs2.size() << endl;
	cout << "bs3:" << bs3.size() << endl;

5.test( )

返回该位在不在:

bool test (size_t pos) const;

返回bs2的每一位在不在: 

	cout << boolalpha;//把布尔值打印成true或false
	for (size_t i = 0; i < bs2.size(); i++)
	{
		cout << bs2.test(i) << endl;
	}

 

6.any( )

是否有至少1位为1: 

bool any() const;

 判断bs3是否至少有1位为1:

	if (bs3.any())
	{
		cout << "bs3至少有1位为1" << endl;
	}
	else
	{
		cout << "bs3没有1位为1" << endl;
	}

 

7.none( )

是否全为0: 

bool none() const;

判断bs3是否全为0: 

	if (bs3.none())
	{
		cout << "bs3全为0" << endl;
	}
	else
	{
		cout << "bs3不全为0" << endl;
	}

 

8.all( )

是否所有位都为1: 

bool all() const noexcept;

 判断bs3是否全为1:

	if (bs3.all())
	{
		cout << "bs3全为1" << endl;
	}
	else
	{
		cout << "bs3不全为1" << endl;
	}

9.set( )

标记分以下两种: 

bitset& set();//每个bit位都标记为1
bitset& set (size_t pos, bool val = true);//把第pos位标记为val

将bs4全标记为1 ,再将第6位标记为0,再将第6为标记为1:

	bitset<16> bs4;
	bs4.set();
	cout << bs4 << endl;
	bs4.set(6,0);
	cout << bs4 << endl;
	bs4.set(6, 1);
	cout << bs4 << endl;

 

10.reset( )

删除分为以下两种:

bitset& reset();//删除所有位,每一位都置为0
bitset& reset (size_t pos);//删除指定pos位,置为0

先删除bs5的第2位的标记 ,再删除所有位的标记:

	bitset<16> bs5(0xFFFF);
	cout << bs5 << endl;
	bs5.reset(2);
	cout << bs5 << endl;
	bs5.reset();
	cout << bs5 << endl;

 

11.flip( )

取反分为以下两种:

bitset& flip();//全部取反
bitset& flip (size_t pos);//把第pos位取反

先对bs5的第2位取反,再对bs5全部取反: 

	bitset<16> bs5(0xFFFF);
	cout << bs5 << endl;
	bs5.flip(2);
	cout << bs5 << endl;
	bs5.flip();
	cout << bs5 << endl;

 

三、位图实现

1.位图定义

位图只需要定义一个成员_bits即可,表明传入的数据个数:

#pragma once
#include<assert.h>
namespace delia
{
	template<size_t N>//数据总数
	class BitSet
	{
	private:
		vector<int> _bits;
	};
}

2.构造

1个字节可以表示32个数在不在,因此只需要申请N/32+1个空间,向上取整

	public:
		BitSet()
		{
			_bits.resize(N/32+1,0)//向上取整,否则不够存,默认每一位标记为0
		}

3.标记

只把该位置1,其他位都不变:

①计算x在位图中的第几个整数的第几个位置

②要把该位置1,就必须让该位和1按位或,且其他位都不能被改变

		//标记
		void Set(size_t x)
		{
			assert(x < N);

			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位

			size_t i = x / 32;
			size_t j = x % 32;

			//_bits[i]的第j位标记为1,并且不影响其他位
			_bits[i] |= (1 << j);
		}

4.删除

只把该位置0,其他位都不变:

①计算x在位图中的第几个整数的第几个位置

②要把该位置0,就必须让该位和0按位与,且其他位都不能被改变

③把1左移j位再按位取反,就可以将该位置为0。

		//删除
		void Reset(size_t x)
		{
			assert(x < N);

			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位
			
			size_t i = x / 32;
			size_t j = x % 32;

			//_bits[i]的第j位标记为0,并且不影响其他位,
			_bits[i] &= (~(1 << j));			
		}

5.检验在不在

①计算x在位图中的第几个整数的第几个位置

②返回该位和1左移j位置后相与的结果

		//检验在不在
		void Test(size_t x)
		{
			assert(x < N);
			
			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位

			size_t i = x / 32;
			size_t j = x % 32;

			//如果第j位为1,结果是非0,非0为真
			//如果第j位为0,结果是0,0为假

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

6.完整代码段

#pragma once
#include<assert.h>
#include<vector>
using namespace std;

namespace delia
{
	template<size_t N>
	class BitSet
	{
	public:
		BitSet()
		{
			_bits.resize(N/32+1,0)//向上取整,否则不够存,默认每一位标记为0
		}

		//标记
		void Set(size_t x)
		{
			assert(x < N);

			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位

			size_t i = x / 32;
			size_t j = x % 32;

			//_bits[i]的第j位标记为1,并且不影响其他位
			_bits[i] |= (1 << j);
		}

		//删除
		void Reset(size_t x)
		{
			assert(x < N);

			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位
			
			size_t i = x / 32;
			size_t j = x % 32;

			//_bits[i]的第j位标记为0,并且不影响其他位,
			_bits[i] &= (~(1 << j));			
		}

		//检验在不在
		void Test(size_t x)
		{
			assert(x < N);
			
			//计算x映射的位在第i个整数
			//计算x映射的位在这个整数的第j位

			size_t i = x / 32;
			size_t j = x % 32;

			//如果第j位为1,结果是非0,非0为真
			//如果第j位为0,结果是0,0为假

			return _bits[i] & (1 << j);
		}
	private:
		vector<int> _bits;
	};

	void test_BitSet()
	{
		BitSet<4294967295u>* bs=new BitSet<4294967295u>;//40亿个数据向上取整,即开辟2^32个bit位
	}
}

四、位图应用

1.给定两个文件,这两个文件分别都有100亿个整数,现在只有1G内存,如何找两个文件交集?

方案(1):依次读取第一个文件中的所有整数并标记映射到一个位图,再读取第二个文件中的所有整数,判断在不在位图,在就是交集中的书,不在就不是

方案(2):依次读取第一个文件的所有证书标记映射为位图1,一次读取第二个文件的所有证书标记映射为位图2,再对两个位图进行相与,结果为1的位就是交集。

无论方案(1)还是方案(2),由于整数最多只有2^32=4294967295个,42亿多个,文件有100亿个数据,肯定有重复的,因此只需要开辟2^32个bit位就能覆盖到这100亿个数据。

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

方案:位图是一个bit位标记一个值,现在用两个bit位标记一个值,位图能标记32个值在不在,那么现在只能标记16个值在不在:

00    出现0次

01    出现1次

10    出现2次及以上

11    不考虑

最后找出01标记的整数

#define  _CRT_SECURE_NO_WARNINGS  1
#pragma once
#include<assert.h>
#include<iostream>
#include<vector>
#include<bitset>
using namespace std;

int main()
{
	vector<int> v = { 3,67,259,1,7890,4,36,23 };

	bitset<4294967295u>* bs1 = new bitset<4294967295u>;
	bitset<4294967295u>* bs2 = new bitset<4294967295u>;

	for (auto e : v)
	{
		if (!bs1->test(e) && !bs2->test(e))//00->01,第一次出现
		{
			bs2->set(e);
		}
		else if (!bs1->test(e) && bs2->test(e))//01->10,第二次出现
		{
			bs1->set(e);
			bs2->reset(e);
		}
		else if (bs1->test(e) && !bs2->test(e))//10->11,第三次出现
		{
			//不用处理
		}
		else//第四次出现
		{
			//不用处理
			assert(false);
		}
	}

	for (size_t i = 0; i < 4294967295; i++)
	{
		if (!bs1->test(i) && bs2->test(i))//找01
		{
			cout << i << endl;
		}
	}
	return 0;
}

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

现在用两个bit位标记一个值, 

00    出现0次

01    出现1次

10    出现2次

11    出现3次

最后找出01和10标记的,即出现1次和出现2次的整数

#define  _CRT_SECURE_NO_WARNINGS  1
#pragma once
#include<assert.h>
#include<iostream>
#include<vector>
#include<bitset>
using namespace std;

int main()
{
	vector<int> v = { 3,67,259,1,7890,4,36,23 };

	bitset<4294967295u>* bs1 = new bitset<4294967295u>;
	bitset<4294967295u>* bs2 = new bitset<4294967295u>;

	for (auto e : v)
	{
		if (!bs1->test(e) && !bs2->test(e))//00->01,第一次出现
		{
			bs2->set(e);
		}
		else if (!bs1->test(e) && bs2->test(e))//01->10,第二次出现
		{
			bs1->set(e);
			bs2->reset(e);
		}
		else if (bs1->test(e) && !bs2->test(e))//10->11,第三次出现
		{
			bs1->set(e);
		}
		else//第四次出现
		{
			//不用处理
			assert(false);
		}
	}

	//找01或10
	for (size_t i = 0; i < 4294967295; i++)
	{
		if ((!bs1->test(i) && bs2->test(i))
			|| (bs1->test(i) && !bs2->test(i))
		{
			cout << i << endl;
		}
	}

	return 0;
}

相关文章:

  • Git 详细安装教程
  • 如何做一个最便宜的小程序?
  • 基于uclinux 的CAN 总线嵌入式驱动编程
  • IPV6基础知识简介
  • 【PE806】Nim on Towers of Hanoi(DP)(生成函数)
  • FFmpeg工具使用总结
  • 打电话用蓝牙耳机什么牌子好?打电话清晰的蓝牙耳机推荐
  • OpenAI 开源语音识别 Whisper
  • 陈齐彦:云原生,抵达元宇宙的数字基石
  • WEB自动化测试(1)—— Cypress 介绍
  • C#把数据库表里简体字转化为繁体字
  • JAVA计算机毕业设计云音乐后端内容管理系统Mybatis+系统+数据库+调试部署
  • Vue基础之插槽、自定义指令、render函数、过滤器
  • 企业实践开源的动机
  • 【力扣刷题】Day06——哈希表专题
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • DOM的那些事
  • gulp 教程
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Next.js之基础概念(二)
  • nodejs:开发并发布一个nodejs包
  • redis学习笔记(三):列表、集合、有序集合
  • 开源SQL-on-Hadoop系统一览
  • 利用DataURL技术在网页上显示图片
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • MPAndroidChart 教程:Y轴 YAxis
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #define与typedef区别
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #pragam once 和 #ifndef 预编译头
  • #QT(TCP网络编程-服务端)
  • #每日一题合集#牛客JZ23-JZ33
  • (day6) 319. 灯泡开关
  • (done) 两个矩阵 “相似” 是什么意思?
  • (第27天)Oracle 数据泵转换分区表
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (七)理解angular中的module和injector,即依赖注入
  • (转载)Linux 多线程条件变量同步
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • ***利用Ms05002溢出找“肉鸡
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .gitignore
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .net 程序发生了一个不可捕获的异常
  • .NET 自定义中间件 判断是否存在 AllowAnonymousAttribute 特性 来判断是否需要身份验证
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .NET/C# 使窗口永不获得焦点
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • .so文件(linux系统)
  • /dev/sda2 is mounted; will not make a filesystem here!
  • :not(:first-child)和:not(:last-child)的用法