位图(bitset)的使用【STL】

简介: 位图(bitset)的使用【STL】

1. 介绍

1.1 背景

一道面试题:

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

到目前为止,我们能想到的最快的办法有两种:

  1. 排序+二分查找;
  2. 用搜索树如红黑树、哈希表等查找效率非常高的数据结构查找。

虽然从时间复杂度上看,它们的效率还可以,一个是O ( N ∗ l o g 2 N ) O(N*log_2N)O(Nlog2N),一个是O ( N ) O(N)O(N),但是这个问题从一开始就和一般的查找问题不同:

“40亿个不重复的无符号整数”,在计算机眼中,一个unsigned整数是4个字节(32位机器),如果这40亿个整数排序后是从0连续递增的(unsigned能表示整数的范围能到42亿9千万,40亿是为了好看),那么40亿个就是40亿*4字节,大约是15GB。这么大的数据量,使用哈希表和搜索树都不大可能有效,因为它们作为一种数据结构本身就占有一定的内存空间,例如结点类的大小。每个结点除了数据本身还有其他结构附带的内存空间占用,哈希表能达到45GB左右,搜索树更是达到了接近60GB。而一般的机器内存并没有这么大,排除第二种方案。

排序+二分查找也不行,还是因为数据量太大,只能作为磁盘文件处理,但是二分查找和外排序都很慢,造成它们速度慢的主要原因是磁盘查找扇区的速度很慢。

即使是强如SSD这样的能够高速读写的固态硬盘,它的读写速度和内存依然相形见绌,所以它的“高速”是相对于传统机械硬盘而言的。

位图就是解决诸如“大海捞针”这样的海量数据问题的。

1.2 概念

位图(bitmap),将每一个bit位的0和1作为集合中某个元素的状态:

  • 0:不存在
  • 1:存在

常用于解决海量数据处理和数据查重这类问题,是一种较高空间利用率的数据结构。

漫画:Bitmap算法

1.3 应用

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

上图中的比特位序号从右到左递增,说明机器是小端机,大多数机器都是小端机。

友情链接:大小端模式

在32位机器中,每个unsigned整数都是4个字节,那么对于上面这个例子,40亿个unsigned整数对应40亿个比特位,一个整数有32个比特位,那么40亿个数也就占512MB。内存消耗极大减少。

2. 位图的使用

STL标准库内置了位图,它叫做bitset

2.1 原型

template <size_t N> class bitset;
  • N:bitset 的大小,以位数表示。

它被包含在头文件<bitset>中。

2.2 构造位图

主要有三种构造位图的方法:

  1. 构造一个16位的位图,默认每位都是0:
bitset<16> bs1; // 0000000000000000
  1. 用一个具体的数值的二进制序列构造位图:
bitset<16> bs2(0xffffffff); // 1111111111111111
  1. (必须)用一个由0和1组成的字符串构造位图:
bitset<16> bs3(string("1010101001")); // 0000001010101001

2.3 常用接口

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

2.4 示例

void test1()
{
  bitset<8> bs;
  cout << "bitset<8> bs:" << bs << endl;
  bs.set();     // 设置所有位
  cout << "bs.set():    " << bs << endl;
  bs.flip();      // 反转所有位
  cout << "bs.flip():   " << bs << endl;
  bs.set(1);      // 设置第1位
  cout << "bs.set(1):   " << bs << endl;
  bs.reset(1);    // 清空第1位
  cout << "bs.reset(1): " << bs << endl;
  bs.flip(1);     // 反转第1位
  cout << "bs.flip(1):  " << bs << endl;
  int size = bs.size();// 可表示位的个数
  cout << "bs.size():   " << size << endl;
  bool any = bs.any(); // 任何一个位被设置返回true
  cout << "any be setted:" << any << endl;
  bs.reset();      // 清空所有位
  bool none = bs.none();// 没有位被设置返回true
  cout << "none be setted:" << none << endl;
}

输出

bitset<8> bs:00000000
bs.set():    11111111
bs.flip():   00000000
bs.set(1):   00000010
bs.reset(1): 00000000
bs.flip(1):  00000010
bs.size():   8
any be setted:1
none be setted:1

2.4 常用运算符

2.4.1 >>和<<

bitset容器重载了>>和<<运算符(流插入和流输出),所以可以直接对容器实例化出的对象进行输入输出操作:

void test2()
{
  bitset<8> bs;
  cin >> bs;
  cout << bs << endl;
}

输入:

1010

输出:

00001010

2.4.2 赋值运算符、关系运算符、复合赋值运算符、单目运算符

  • 赋值运算符:=;
  • 关系运算符:==、!=;
  • 复合赋值运算符:&=、|=、^=、<<=、>>=;
  • 单目运算符:~。
void test3()
{
  bitset<8> bs1(string("11100000"));
  bitset<8> bs2(string("00000111"));
  bool eql = bs1 != bs2;
  cout << "bs1!=bs2:  " << eql << endl;
  bs1 >>= 3;
  cout << "bs1>>3:    " << bs1 << endl;
  bs2 ^= bs1;
  cout << "bs2 ^= bs1:" << bs2 << endl;
}

输出:

bs1!=bs2:  1
bs1>>3:    00011100
bs2 ^= bs1:00011011

2.4.3 位运算符

位图也可以直接用三个位运算符对位操作:

void test4()
{
  bitset<8> bs1(string("10101010"));
  bitset<8> bs2(string("01010101"));
  cout << (bs1 & bs2) << endl;
  cout << (bs1 | bs2) << endl;
  cout << (bs1 ^ bs2) << endl;
}

输出:

00000000
11111111
11111111

2.4.4 [ ]运算符

位操作作为计算机中最精细的操作,速度理应是非常快的,所以可以认为是像数组一样随机访问不同序号的比特位:

void test5()
{
  bitset<8> bs(string("10101010"));
  cout << bs[1] << endl;
  bs[7] = 0;
  cout << bs << endl;
}

输出:

1
00101010
目录
相关文章
|
项目管理 开发工具 git
git push 报错 pre-receive hook declined
git push 报错 pre-receive hook declined
5095 0
git push 报错 pre-receive hook declined
|
SQL 关系型数据库 MySQL
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
234 0
|
存储 人工智能 算法
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
|
8月前
|
人工智能 自然语言处理 Linux
5分钟Deepseek R1本地化部署
DeepSeek R1 是一款基于Transformer架构的先进大语言模型,以其强大的自然语言处理能力和高效的推理速度著称。本文介绍如何通过开源框架Ollama在本地快速部署DeepSeek R1。Ollama简化了大型语言模型的部署过程,支持多种操作系统和模型格式,提供便捷的安装、启动及API接口,使得研究人员和开发者能轻松运行和定制模型。通过简单的命令行操作和HTTP API,用户可以在本地环境中高效利用DeepSeek R1的强大功能。
715 5
阿里云短信服务的计费方式、规则和欠费说明_短信服务
阿里云短信服务的计费方式、规则和欠费说明_短信服务,阿里云短信服务价格表,阿里云短信0.032元一条,阿里云短信价格?阿里云短信怎么收费?阿里云短信多少钱一条,阿里云短信价格0.032元一条
1054 0
|
11月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
312 2
|
11月前
|
存储 持续交付 开发工具
clang-format
clang-format
770 7
|
存储 消息中间件 算法
深入解析OpenStack Cinder:块存储服务详解
本文介绍了OpenStack及其块存储服务Cinder。OpenStack是一个开源云计算管理平台,提供基础设施即服务(IaaS),核心服务包括计算、网络、存储等。Cinder主要用于为虚拟机提供持久性块存储,具备多种功能,如卷操作、备份、快照及与实例的交互等。此外,还详细介绍了Cinder的工作流程、命令行操作及不同存储插件的使用。
1506 8
|
人工智能 数据可视化 数据处理
推荐2款免费开源的标注工具,支持大模型对话标注
【LabelLLM】一款开源免费的大模型对话标注平台,专为优化大型语言模型的数据标注过程设计。支持灵活配置与多模态数据(音频、图像、视频),具备全面任务管理和AI辅助标注功能,大幅提升标注效率与准确性。了解更多请前往https://github.com/opendatalab/LabelLLM 【LabelU】一款轻量级开源标注工具,支持图像、视频、音频的高效标注。特色功能包括多功能图像处理、视频和音频分析等,简易灵活,支持多种数据格式输出。了解更多请前往https://github.com/opendatalab/labelU
2826 11
|
API Python
Python实现大麦网抢票的四大关键技术点解析
随着互联网的普及和发展,线上购票已经成为人们生活中不可或缺的一部分。然而,在抢购热门演出门票时,往往会遇到抢票难、抢票快的问题,有时候一秒钟的延迟就意味着与心仪的演出擦肩而过。为了解决这个问题,技术爱好者们开始探索利用Python多线程技术来提高抢票效率。本文将介绍Python实现大麦网抢票的四大关键技术点,帮助读者了解抢票脚本的核心原理,并通过示例代码详细说明实现过程。