文章目录

  • 1. 介绍
    • 1.1 背景
    • 1.2 概念
      • 1.3 应用
  • 2. 位图的使用
    • 2.1 原型
    • 2.2 构造位图
    • 2.3 常用接口
    • 2.4 示例
    • 2.4 常用运算符
      • 2.4.1 >>和<<
      • 2.4.2 赋值运算符、关系运算符、复合赋值运算符、单目运算符
      • 2.4.3 位运算符
      • 2.4.4 [ ]运算符

1. 介绍

1.1 背景

一道面试题:

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

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

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

虽然从时间复杂度上看,它们的效率还可以,一个是 O ( N ∗ l o g 2 N ) O(N*log_2N) O(N∗log2​N),一个是 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
    
  2. 用一个具体的数值的二进制序列构造位图:

    bitset<16> bs2(0xffffffff); // 1111111111111111
    
  3. (必须)用一个由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(); // 任何一个位被设置返回truecout << "any be setted:" << any << endl;bs.reset();             // 清空所有位bool none = bs.none();// 没有位被设置返回truecout << "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

位图(bitset)的使用【STL】相关推荐

  1. Effective STL 50条有效使用STL的经验笔记

    Scott Meyers大师Effective三部曲:Effective C++.More Effective C++.Effective STL,这三本书出版已很多年,后来又出版了Effective ...

  2. Effective STL读书笔记

    第一章容器 条款1:仔细选择你的容器 C++中各种标准或非标容器: 标准STL序列容器:    vector.string.deque和list(双向列表). 标准STL管理容器:    set.mu ...

  3. 《Effective STL》中文版 读书笔记

    50条有效使用STL的经验 第一条 慎重选择容器类型(20190713) 第二条 不要试图编写独立于容器类型的代码(20190713) 第三条 确保容器中的对象副本正确而高效(20190713) 第四 ...

  4. LeetCode 41

    这个题是找出无序数组里面的第一个不连续的正整数,而且时间复杂度不超过O(n).首先分析一下:数组是无序的,需要找出第一个不连续的数字,而排序的算法一般很难达到O(n)的时间复杂度.这个时候我们必须得明 ...

  5. c++ - 第18节 - 哈希

    目录 1.unordered系列关联式容器 1.1.unordered_set 1.1.1.unordered_set的介绍 1.1.2.unordered_set的使用 1.2.unordered_ ...

  6. 为什么 ElasticSearch 比 MySQL 更适合复杂条件搜索

    熟悉 MySQL 的同学一定都知道,MySQL 对于复杂条件查询的支持并不好.MySQL 最多使用一个条件涉及的索引来过滤,然后剩余的条件只能在遍历行过程中进行内存过滤. 上述这种处理复杂条件查询的方 ...

  7. 为什么ElasticSearch比MySQL更适合复杂条件搜索

    熟悉 MySQL 的同学一定都知道,MySQL 对于复杂条件查询的支持并不好.MySQL 最多使用一个条件涉及的索引来过滤,然后剩余的条件只能在遍历行过程中进行内存过滤. 上述这种处理复杂条件查询的方 ...

  8. C++学习系列笔记(八)

    1.STL映射类 STL map和multimap的内部结构看起来像棵二叉树.这意味着在map或multimap中插入元素时将进行排序.要使用STL map或multimap类,需要包含头文件< ...

  9. JAVA编程思想——读书笔记 对象的容纳

    文章目录 对象的容纳 1.数组(array) 数组和第一类对象 基本数据类型集合 数组的返回 2.集合(collection) 集合的缺点:类型未知 它不适用于一下场合: 1.错误有时显露不出来 生成 ...

最新文章

  1. Unity版本使用情况统计报告
  2. 成功移植mplayer到mini2440
  3. 读薄《高性能MySql》(四)查询性能优化
  4. JUL执行原理和流程
  5. 数据仓库相关书籍调研
  6. HBase实战:记一次Safepoint导致长时间STW的踩坑之旅
  7. html中md5如何使用方法,html中使用js進行登錄md5加密提交並重定向新頁面
  8. 分布式系统中的序列化与反序列化
  9. CMMI认证适用的行业范围
  10. 计算机系统动态库修复,win10系统提示dll动态链接库出现故障修复的处理步骤
  11. word2007自动生成目录
  12. MATLAB 插值函数运用 - interp1
  13. 经典算法题:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号。
  14. npm install 安装一直报错Error EPERM operation not permitted, mkdir
  15. linux系统外接硬盘_linux指定某用户某组挂载外接硬盘以便操作硬盘
  16. ICLR2020国际会议精彩演讲抢先看(含源码)!!
  17. python qq自动发消息软件_Python之qq自动发消息的示例代码
  18. 【YOLO v4 相关理论】Normalization: BN、CBN、CmBN
  19. 新手学做网站的建议教程
  20. cortex m3/m4处理器的复位设计

热门文章

  1. 如何开发微信礼品卡-服务端
  2. C语言推箱子(带回退,撤回,返回上一步功能)
  3. 华为设备配置OSPF负载分担
  4. 《数》孙溟㠭先生篆刻
  5. 优思学院|“元宇宙“是什么东西?
  6. 讲解一些复变函数的基础概念
  7. 英特尔OneAPI介绍
  8. 桌面虚拟化-精彩刚刚开始
  9. 开发者必备的顶级Android开发工具,成功入职阿里
  10. 硬链接与软连接的区别_(转)