【C++】STL详解(十四)—— bitset(位图)的模拟实现

news2024/9/24 15:18:41

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++学习
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【C++】STL详解(九)—— set、map、multiset、multimap的介绍及使用

文章目录

  • bitset类各函数接口总览
  • bitset类的实现
    • 构造函数
    • set、reset、flip、test
    • size、count
    • any、none、all
    • 打印函数
  • 总结:

bitset类各函数接口总览

namespace sherry
{
	//模拟实现位图
	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; //位图
	};
}

注意:为了防止与标准库当中的bitset类产生命名冲突,模拟实现时需放在自己的命名空间当中。

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、reset、flip、test

set成员函数用于设置位。

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

计算出该位位于第 i 个整数的第 j 个比特位。
将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成员函数用于清空位。

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

计算出该位位于第 i 个整数的第 j 个比特位。
将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(不影响其他位)
}

flip成员函数用于反转位。

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

计算出该位位于第 i 个整数的第 j 个比特位。
将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成员函数用于获取位的状态。

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

计算出该位位于第 i 个整数的第 j 个比特位。
将1左移 j 位后与第 i 个整数进行与运算得出结果。
若结果非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的个数的方法如下:

将原数 n 与 n - 1 进行与运算得到新的 n 。
判断 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成员函数用于判断位图中是否全部位都被设置。

判断过程分为两步:

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

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

代码如下:

//判断位图中是否全部位都被设置
bool all()
{
	size_t n = _bits.size();
	//先检查前n-1个整数
	for (size_t i = 0; i < n - 1; i++)
	{
		if (~_bits[i] != 0) //取反后不为全0,说明取反前不为全1
			return false;
	}
	//再检查最后一个整数的前N%32位
	for (size_t j = 0; j < N % 32; j++)
	{
		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; //打印总共打印的位的个数
}

总结:

今天我们比较详细地完成了bitset(位图)的模拟实现,了解了一些有关的底层原理。接下来,我们将进行布隆过滤器的学习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

日常学习记录随笔-seata

我们知道所有的事务都要满足ACID的原则,也就是原子性 一致性 隔离性 持久性 在单体架构中服务访问db.基于数据库本身的特性就能够实现acid 微服务的架构比较复杂 可能一个业务要跨越多个服务 每个服务又会有自己的db库 再靠数据库本身的特性 还能保证acid么这个业务 就包含3…

翻译:网站整站翻译 / 网站国际化 / 极简实现

一、本文目标 以极简单的方法实现整站翻译&#xff0c;轻松实现国际化。 二、js 文件 https://res.zvo.cn/translate/translate.js 三、代码 代码放在浏览器控制台即可实现 var head document.getElementsByTagName(head)[0];var script document.createElement(script);sc…

【算法|动态规划No.17】leetcode64. 最小路径和

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

多路彩灯控制器LED流水灯花型verilog仿真图视频、源代码

名称&#xff1a;多路彩灯控制器LED流水灯花型verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 用quartus和modelism&#xff0c;设计一个多路彩灯控制器&#xff0c;能够使花型循环变化&#xff0c;具有复位清零功能&#xff0c;并可以选择…

unity操作_光源组件 c#

准备工作 添加资源导入后先不管&#xff0c;现在主要学习自带Directional Light 我们首先创建一个平面Plane 然后重置一下位置 然后创建一个Cube 也重置一下位置然后修改y0.5刚好在这个平面上 ctrl d复制一个Cube 修改位置和旋转角度 给物体一个颜色 接下来创建一个点光源 我们…

基于python判断回文字符串

一、功能程序 可以使用以下代码&#xff0c;来判断一个字符串是否为回文字符串&#xff1a; def is_palindrome(string):# 将字符串转换为小写&#xff0c;并去除空格和标点符号string .join(e for e in string.lower() if e.isalnum())# 检查字符串是否等于它的反转return …

SpringCloud学习笔记-Ribbon负载均衡

目录 1.负载均衡策略2.自定义负载均衡策略3.饥饿加载 SpringCloudRibbon的底层采用了一个拦截器&#xff0c;拦截了RestTemplate发出的请求&#xff0c;对地址做了修改。用一幅图来总结一下&#xff1a; 基本流程如下&#xff1a; 拦截我们的RestTemplate请求http://userserv…

每日练习-9

目录 1、井字棋 2、密码强度等级 3、二维数组中的查找 4.调整数组奇数偶数 5.旋转数组中的最小元素 6、替换空格 1、井字棋 解析&#xff1a;井字棋有四种情况表示当前玩家获胜&#xff0c;行全为1&#xff0c; 列全为1&#xff0c;主对角全为1&#xff0c; 副对角全为1。遍历…

pytorch学习------TensorBoard的使用

目录 简介使用方式1、单条曲线(scalar)2、多条曲线(scalars)3、直方图(histogram)4、图片(image)5、渲染(figure)6、网络(graph) 简介 建好一个神经网络&#xff0c;其实我们也不知道神经网络里头具体细节到底做了什么&#xff0c;要人工调试十分困难(就好比你无法想象出递归的…

Markdown文本编辑器常用操作介绍

文章目录 标题文本样式列表图片链接目录插入代码插入表格 背景说明 Markdown语言是国际杂志编辑以及许多写作者都广泛使用的写作语言它使用一些简单的符号&#xff08;* / > [] () #&#xff09;来标记文本格式其简洁的语法、优美的格式以及强大的软件支持深受广大网友的喜…

基于蝠鲼觅食优化的BP神经网络(分类应用) - 附代码

基于蝠鲼觅食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于蝠鲼觅食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蝠鲼觅食优化BP神经网络3.1 BP神经网络参数设置3.2 蝠鲼觅食算法应用 4.测试结果…

Java @Override 注解

在代码中&#xff0c;你可能会看到大量的 Override 注解。 这个注解简单来说就是让编译器去读的&#xff0c;能够避免你在写代码的时候犯一些低级的拼写错误。 Java Override 注解用来指定方法重写&#xff08;Override&#xff09;&#xff0c;只能修饰方法并且只能用于方法…

自定义背景画词云图(中文)

背景 词云图还是挺需要用的吧&#xff0c;大量的词中&#xff0c;我们可以快速捕捉到高频的关键的词汇。 环境&#xff1a;python3 对csv文件里面某列所有字符串画词云图&#xff08;中文的字符&#xff09; 新安装环境&#xff1a; 1.下载中文字体SimHei.ttf&#xff0c;并放…

C++DAY41

#include <iostream>using namespace std;class Per { private:string name;int age;float* height;float* weight; public:Per(){cout << "Per::无参构造函数" << endl;}Per(string name,int age,float* height,float* weight):name(name),age(ag…

RustDay01——运行在线GitHub Rust环境

1.跟着教程进入GitHub教室 2. 授权确认后进入学习空间 3.点击链接进入在线平台 4.添加本机密钥对到GitHub 5. 安装依赖 我们使用在线的Linux试验平台&#xff0c;就自动帮我们clone好了仓库 我们直接在仓库目录执行 cargo install --force --path . 安装依赖 PS:其实刚开始…

Godot 添加信号

前言 Godot 里面C#和GDScirpt 的用法完全不一样&#xff0c;网上相关资料太少了。 什么是信号 信号分为信号源&#xff0c;触发&#xff0c;目的节点。信号源在某些条件下触发信号&#xff0c;比如按钮点击&#xff0c;鼠标悬停等事件 #mermaid-svg-wyr9ARVcBFmUUu8y {font-…

哈希传递原理

哈希传递只针对相同密码进行攻击。 NTLM认证流程 NTLM加密&#xff1a; admin -> hex(16进制编码) 61646d696e61646d696e-> Unicode 610064006d0069006e00610064006d0069006e00–>MD4209c6174da490caeb422f3fa5a7ae634 认证过程&#xff1a; 注意:challenge每次认证…

【C++】list基本接口+手撕 list(详解迭代器)

父母就像迭代器&#xff0c;封装了他们的脆弱...... 手撕list目录&#xff1a; 一、list的常用接口及其使用 1.1list 构造函数与增删查改 1.2list 特殊接口 1.3list 排序性能分析 二、list 迭代器实现&#xff08;重点难点&#xff09; 关于迭代器的引入知识&#xff1a…

项目管理入门指南:如何快速上手?

面对复杂的项目任务&#xff0c;如何入手项目管理呢&#xff1f;在项目管理过程中需要面对许多繁杂的问题以及复杂的流程&#xff0c;还涉及到与团队成员的沟通协作。如果单单依靠人的注意力与记忆&#xff0c;可能没有办法兼顾多个方面。一个靠谱好用的项目管理工具可以帮助您…

HTTPS建立连接的过程

HTTPS 协议是基于 TCP 协议的&#xff0c;因而要先建立 TCP 的连接。在这个例子中&#xff0c;TCP 的连接是在手机上的 App 和负载均衡器 SLB 之间的。 尽管中间要经过很多的路由器和交换机&#xff0c;但是 TCP 的连接是端到端的。TCP 这一层和更上层的 HTTPS 无法看到中间的包…