<C++> STL_bitset使用和模拟实现

news2024/11/24 19:18:29

bitset的介绍

位图的引入

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

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

  • 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
  • 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。

单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O(N*LogN),第二种方法的时间复杂度是O(N)。

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

位图解决

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

在这里插入图片描述

无符号整数总共有2的32次方个,因此记录这些数字就需要2的32次方个比特位,也就是512M的内存空间,内存消耗大大减少。

位图的概念

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

位图的应用

常见位图的应用如下:

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

bitset的使用

bitset是一种特殊的容器类,它的每个元素只占用一位,只能为0或1。

bitset的定义方式

创建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:将bitset中的一位或所有位设置为1
  • reset:将bitset中的一位或所有位设置为0
  • flip:翻转bitset中的一位或所有位
  • test:获取指定位的状态
  • count:返回bitset中1的个数
  • size:返回bitset中位的个数
  • any:如果bitset中至少有一位为1,则返回true
  • none:如果bitset中所有位都为0,则返回true
  • all:如果所有位都被设置,则返回true
  • to_string:将bitset转换为字符串
  • to_ulongto_ullong:将bitset转换为无符号(长)整型数

使用示例:

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

int main() {
    bitset<8> bs;
    //位置从0开始
    bs.set(2);         //设置第2位
    bs.set(4);         //设置第4位
    cout << bs << endl;//00010100

    bs.flip();                 //反转所有位
    cout << bs << endl;        //11101011
    cout << bs.count() << endl;//6

    cout << bs.test(3) << endl;//1  3的位置为1

    bs.reset(0);       //清空第0位,第0位变成了0
    cout << bs << endl;//11101010

    bs.flip(7);        //反转第7位
    cout << bs << endl;//01101010

    cout << bs.size() << endl;//8

    cout << bs.any() << endl;//任何位为1,就返回true 1

    bs.reset();               //清空所有位
    cout << bs.none() << endl;//没有位被设置,就返回true 1
 
    bs.set();                //设置所有位
    cout << bs.all() << endl;//所有位被设置,返回true  1

    return 0;
}22211

注意: 使用成员函数set、reset、flip时,若指定了某一位则操作该位,若未指定位则操作所有位。

bitset运算符的使用

一、bitset中>>、<<运算符的使用。
bitset容器对>>、<<运算符进行了重载,我们可以直接使用>>、<<运算符对biset容器定义出来的对象进行输入输出操作。

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

int main() {
    bitset<8> bs;
    cin >> bs;         //10110
    cout << bs << endl;//00010110
    return 0;
}

二、bitset中赋值运算符、关系运算符、复合赋值运算符、单目运算符的使用。

bitset容器中不仅对赋值运算符和一些关系运算符进行了重载,而且对一些复合赋值运算符和单目运算符也进行了重载,我们可以直接使用这些运算符对各个位图进行操作。

包括如下运算符:

  • 赋值运算符:=。
  • 关系运算符:==、!=。
  • 复合赋值运算符:&=、|=、^=、<<=、>>=。
  • 单目运算符:~。

三、bitset中位运算符的使用。
bitset容器中同时也对三个位运算符进行了重载,我们可以直接使用&、|、^对各个位图进行操作。

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

int main() {
    bitset<8> bs1(string("10101010"));
    bitset<8> bs2(string("01010101"));

    cout << (bs1 & bs2) << endl;//00000000
    cout << (bs1 | bs2) << endl;//11111111
    cout << (bs1 ^ bs2) << endl;//11111111
    return 0;
}

四、bitset中[ ]运算符的使用。
bitset容器中对[ ]运算符进行了重载,我们可以直接使用[ ]对指定位进行访问或修改。

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

int main() {
    bitset<8> bs(string("00110101"));
    cout << bs[0] << endl;//1
    bs[0] = 0;
    cout << bs << endl;//00110100
    return 0;
}

bitset的模拟实现

bit类各函数接口总览

namespace cl {
    //模拟实现位图
    template<size_t N>
    class bitset {
    public:
        //构造函数
        bitset();
        //设置位
        void set(size_t pos);
        //清空位
        void reset(size_t pos);
        //反转位
        void flip(size_t pos);
        //获取位的状态
        bool test(size_t pos);
        //获取可以容纳的位的个数
        size_t size();
        //获取被设置位的个数
        size_t count();
        //判断位图中是否有位被设置
        bool any();
        //判断位图中是否全部位都没有被设置
        bool none();
        //判断位图中是否全部位都被设置
        bool all();
        //打印函数
        void Print();

    private:
        vector<int> _bits;//位图
    };
}// namespace cl

bitset类的实现

构造函数

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

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

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

在这里插入图片描述

代码如下:

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

成员函数

set

set成员函数用于设置位。

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

  1. 计算出该位位于第 i 个整数的第 j 个比特位。

  2. 将1左移 j 位后与第 i 个整数进行或运算即可。

    在这里插入图片描述

代码如下

//设置位
void set(size_t pos){
	assert(pos < N);

	//算出pos映射的位在第i个整数的第j个位
	int i = pos / 32;
	int j = pos % 32;
	_bits[i] |= (1 << j); //将该位设置为1(不影响其他位)
}
reset

reset成员函数用于清空位。

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

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位再整体反转后与第 i 个整数进行与运算即可。

在这里插入图片描述

代码如下

//清空位
void reset(size_t pos){
	assert(pos < N);

	//算出pos映射的位在第i个整数的第j个位
	int i = pos / 32;
	int j = pos % 32;
	_bits[i] &= (~(1 << j)); //将该位设置为0(不影响其他位)1进行翻转后只有第j位置位0 其他都是1,所以只会影响第j位置的数字
}
flip

flip成员函数用于反转位。

反转位图中指定的位的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位后与第 i 个整数进行异或运算即可。

在这里插入图片描述

代码如下

//反转位
void flip(size_t pos){
	assert(pos < N);

	//算出pos映射的位在第i个整数的第j个位
	int i = pos / 32;
	int j = pos % 32;
	_bits[i] ^= (1 << j); //将该进行反转(不影响其他位)
}
test

test成员函数用于获取位的状态。

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

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

在这里插入图片描述

代码如下

//获取位的状态
bool test(size_t pos){
	assert(pos < N);

	//算出pos映射的位在第i个整数的第j个位
	int i = pos / 32;
	int j = pos % 32;
	//该比特位被设置
	if (_bits[i] & (1 << j)){
		return true;
	} else {//该比特位未被设置
		return false;
	} 
}
size、count

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

我们直接将所给非类型模板参数进行返回即可。

//获取可以容纳的位的个数
size_t size(){
	return N;
}

count成员函数用于获取位图中被设置的位的个数。

获取位图中被设置的位的个数,也就是统计位图中1的个数,我们只需要依次统计每个整数二进制中1的个数,然后将其相加即可得到位图中1的个数。

统计二进制中1的个数的方法如下:

  1. 将原数n与n - 1进行与运算得到新的 n 。
  2. 判断n是否为0,若 n 不为0则继续进行第一步。

如此进行下去,直到n最终为0,此时该操作进行了几次就说明二进制中有多少个1。

因为该操作每进行一次就会消去二进制中最右边的1

代码如下

//获取被设置位的个数
size_t count(){
	size_t count = 0;
	//将每个整数中1的个数累加起来
	for (auto e : _bits){
		int num = e;
		//计算整数num中1的个数
		while (num){
			num = num&(num - 1);
			count++;
		}
	}
	return count; //位图中1的个数,即被设置位的个数
}
any、none、all

any成员函数用于判断位图中是否有位被设置。

我们只需遍历每一个整数,若这些整数全部都为0,则说明位图中没有位被设置过。
虽然位图可能并没有包含最后一个整数的全部比特位,但由于我们构造位图时是将整数的全部比特位都初始化成了0,因此不会对此处判断造成影响。

在这里插入图片描述

代码如下

//判断位图中是否有位被设置
bool any(){
	//遍历每个整数
	for (auto e : _bits){
		//该整数中有位被设置
		if (e != 0) {
			return true;
		}		
	}
	return false; //全部整数都是0,则没有位被设置过
}

none成员函数用于判断位图中是否全部位都没有被设置。

位图中是否全部位都没有被设置,实际上就是位图中有位被设置的反面,因此none成员函数直接调用any成员函数,然后将返回值取反后再进行返回即可。

//判断位图中是否全部位都没有被设置
bool none() {
	return !any();
}

all成员函数用于判断位图中是否全部位都被设置。

判断过程分为两步:

  1. 先检查前n-1个整数的二进制是否为全1。
  2. 再检查最后一个整数的前N%32个比特位是否为全1。

需要注意的是,如果位图没有包含最后一个整数的全部比特位,那么最后一个整数的二进制无论如何都不会为全1,所以在判断最后一个整数时应该只判断位图所包含的比特位。

在这里插入图片描述

代码如下

//判断位图中是否全部位都被设置
bool all() {
    size_t n = _bits.size();
    //先检查前n-1个整数
    for (size_t i = 0; i < n - 1; i++) {
        //取反后不为全0,说明取反前不为全1
        if (~_bits[i] != 0){
            return false;
        }
    }
    //再检查最后一个整数的前N%32位
    for (size_t j = 0; j < N % 32; j++) {
        //等于0表示没有被设置
        if ((_bits[n - 1] & (1 << j)) == 0){
            return false;
        }
    }
    return true;
}

打印函数

可以实现一个打印函数,便于检查我们上述代码的正确性,打印位图时遍历位图所包含的比特位进行打印即可,在打印位图的过程中可以顺便统计位图中位的个数count,将count与我们传入的非类型模板参数N进行比较,可以判断位图大小是否是符合我们的预期。

//打印函数
void Print() {
    int count = 0;
    size_t n = _bits.size();
    //先打印前n-1个整数
    for (size_t i = 0; i < n - 1; i++) {
        for (size_t j = 0; j < 32; j++) {
            if (_bits[i] & (1 << j)) {
                cout << "1";
            } else {
                cout << "0";
            }
            count++;
        }
    }
    //再打印最后一个整数的前N%32位
    for (size_t j = 0; j < N % 32; j++) {
        if (_bits[n - 1] & (1 << j)){
            cout << "1";
        }
        else{
            cout << "0";
        }
        count++;
    }
    cout << " " << count << endl;//打印总共打印的位的个数
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1057064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习必修课 - 如何处理缺失数据

运行环境&#xff1a;Google Colab 处理缺失数据可简单分为两种方法&#xff1a;1. 删除具有缺失值的列 2. 填充 !git clone https://github.com/JeffereyWu/Housing-prices-data.git下载数据集 import pandas as pd from sklearn.model_selection import train_test_split导…

EM@常用三角函数图象性质(中学部分)

文章目录 abstract正弦函数正弦型函数转动相关概念旋转角速度转动周期转动频率初相小结 余弦函数的图象与性质性质 正切函数的图象和性质由已知三角函数值求角任意角范围内反三角函数(限定范围内)反正弦反余弦反正切 abstract 讨论 sin ⁡ , cos ⁡ , tan ⁡ \sin,\cos,\tan s…

WEB 3D 技术,通过node环境创建一个three案例

好 文章 前端3D Three.js 在本地搭建一个官方网站 中我们 搭建了一个Three的官网 现在呢 我们就来创建第一个ThreeJs的资源 这里呢 我们还是选择一个脚手架的开发模式 因为现在基本所有的前端都在使用这样的开发方式 这里 我们创建一个文件夹目录 作为我们项目的存放目录 我们…

【MySQL教程】| (1-1) 2023MySQL-8.1.0 安装教程

文章目录 一、安装包下载二、安装配置1、解压安装包2、编写MySQL配置文件3、初始化MySQL数据库3、安装mysql服务并启动4、MySQL服务5、连接MySQL6、修改密码 三、配置环境变量四、防止mysql自启动拖慢开机时间 近日有粉丝问到mysql在win11的安装中遇到一些问题&#xff0c;应粉…

基于 QT 实现 Task Timer,高效利用时间

一、开发环境 Ubuntu 20.04 QT6.0 二、新建 Qt Wigets Application 这里的基类选择 Wigets&#xff0c; pro 配置文件添加 sql 模块&#xff0c;需要用到 sqlite&#xff0c; QT sql 三、添加数据库连接头文件 // connection.h #ifndef CONNECTION_H #define CONNECTION_…

pycharm配置python3.8版本专门用于undecteded_chromedriver测试

pycharm配置python3.8版本专门用于undecteded_chromedriver测试 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://pay.xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、Pycharm及python环境的配置 1.安装python-3.8.7rc1-amd64.e…

QT常用控件介绍

QT信号与槽机制 connect (A,SIGNLA(aaa())&#xff0c;B, SLOT(bbb()))&#xff1b; GUI继承简介 布局管理器 垂直布局水平布局网格布局表单布局 输出控件 Label: 标签Text Browser: 文本浏览器Graphics View : 图形视图框架Calendar Widget: 日历控件LCD Number: 液晶字体数…

游戏逆向中的 NoClip 手段和安全应对方式

文章目录 墙壁边界寻找碰撞 NoClip 是一种典型的黑客行为&#xff0c;允许你穿过墙壁&#xff0c;所以 NoClip 又可以认为是避免碰撞体积的行为 墙壁边界 游戏中设置了碰撞体作为墙壁边界&#xff0c;是 玩家对象 和墙壁发生了碰撞&#xff0c;而不是 相机 玩家对象有他的 X…

操作系统初探 - 进程的概念

目录 预备知识 冯诺依曼和现代计算机结构 操作系统的理解 进程和PCB的概念 PCB中的信息 查看进程信息的指令 - ps pid 进程状态 预备知识 在学习操作系统之前我们需要先了解一下如下的预备知识。 冯诺依曼和现代计算机结构 美籍匈牙利科学家冯诺依曼最先提出“程序存…

【CAD二次开发】给CAD添加TRUSTEDPATHS避免dll插件信任弹窗

找到配置文件目录,遍历下面的每个配置文件; 找到 Variables 下的TRUSTEDPATHS项目;在后面添加新的目录即可,多个目录使用分号分隔; public static void AddPath(string trusedPath){// 指定注册表键的路径

画CMB天图使用Planck配色方案

使用Planck的配色方案&#xff1a; 全天图&#xff1a; 或者方形图&#xff1a; 使用下面设置即可&#xff1a; import pspy, pixell from pspy.so_config import DEFAULT_DATA_DIR pixell.colorize.mpl_setdefault("planck")此方法不会改变matplotlib默认配色方案…

浏览器指定DNS

edge--设置 https://dns.alidns.com/dns-query

JavaSE | 初识Java(六) | 数组 (上)

数组的创建及初始化 T[] 数组名 new T[N]; //T&#xff1a;表示数组中存放元素的类型 //T[]&#xff1a;表示数组的类型 //N&#xff1a;表示数组的长度 int[] array1 new int[10]; // 创建一个可以容纳10个int类型元素的数组 double[] array2 new double[5]; // 创建一个可…

再次总结nios II 下载程序到板子上时出现 Downloading RLF Process failed的问题

之前也写过两篇关于NIOS II 出现&#xff1a;Downloading RLF Process failed的问题&#xff0c;但是总结都不是很全面&#xff0c;小梅哥的教程总结的比较全面特此记录。 问题&#xff1a;nios II 下载程序到板子上时出现 Downloading RLF Process failed的问题。 即当nios中…

Google vs IBM vs Microsoft: 哪个在线数据分析师证书最好

Google vs IBM vs Microsoft: 哪个在线数据分析师证书最好&#xff1f; 对目前市场上前三个数据分析师证书进行审查和比较|Madison Hunter 似乎每个重要的公司都推出了自己版本的同一事物&#xff1a;专业数据分析师认证&#xff0c;旨在使您成为雇主的下一个热门商品。 随着…

Python 无废话-基础知识元组Tuple详讲

“元组 Tuple”是一个有序、不可变的序列集合&#xff0c;元组的元素可以包含任意类型的数据&#xff0c;如整数、浮点数、字符串等&#xff0c;用()表示&#xff0c;如下示例&#xff1a; 元组特征 1) 元组中的各个元素&#xff0c;可以具有不相同的数据类型&#xff0c;如 T…

【APUE】文件系统 — 目录和文件

目录 1、获取文件属性 2、文件访问权限 3、umask 4、文件权限管理 4.1 chmod 4.2 fchmod 5、粘住位 6、文件系统举例 6.1 FAT系统 6.2 UFS系统 6.3 补充 7、链接 7.1 硬链接 7.2 符号链接 7.3 相关函数 7.3.1 link 7.3.2 unlink 7.3.3 remove 7.3.4 re…

JavaWeb项目:smbms(mysql)

1.准备工作&#xff0c;创建数据库 CREATE DATABASE smbms;USE smbms;CREATE TABLE smbms_address (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID,contact VARCHAR(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 联系人姓名,addressDesc VARCHAR(50) COLLATE u…

字符串,字符数组,类型转换,整数越界,浮点数,枚举

目录 自动类型转换 强制类型转换 数据类型 sizeof 数据类型所占字节数 整数越界 浮点数 字符型 字符串变量 ​编辑字符串的输入输出 main函数的参数 &#xff0c;argc,argv 单个字符输入输出 putchar getchar strlen,strcmp,strcat,strchr,strstr strlen 求字…

数学建模Matlab之检验与相关性分析

只要做C题基本上都会用到相关性分析、一般性检验等&#xff01; 回归模型性能检验 下面讲一下回归模型的性能评估指标&#xff0c;用来衡量模型预测的准确性。下面是每个指标的简单解释以及它们的应用情境&#xff1a; 1. MAPE (平均绝对百分比误差) 描述: 衡量模型预测的相对…