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

Cpp::STL—位图bitset的使用与模拟实现(39)

文章目录

  • 前言
  • 一、先来个题目引入
  • 二、位图概念
  • 三、位图的应用
  • 四、位图的使用
  • 五、模拟实现
    • 接口总览
    • 构造函数
    • set、reset、flip、test
    • size、count
    • any、none、all
  • 总结


前言

  Hello,我们又倒回来更新C++篇了
  好焦虑,时常在想着该如何调节~~


一、先来个题目引入

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

    其实我第一反应是放到一个数组里面,然后排序,再二分查找;或者放到容器unordered_set里面,然后find一下,其实这样看起来还可以,第一种方法时间复杂度为 O(NlogN) ,第二种方法时间复杂度为 O(N) ,但是现在有一个很严肃的问题就是 40亿 个数据, 那一共需要 40 * 10^8 * 4 个Byte,然后再将这个数 / 1024 / 1024 / 1024 ≈ 16GB,内存很显然是放不了那么大的数据的,所以上述两个方法显然不太可行

二、位图概念

  在上面这个问题中,我们只需要判断数据在不在,那么其实也就是两种状态,我们很容易联想到 0, 1 ,也就是说,表达一个数据在不在的话,根本不需要四个字节,其实只需要一个 bit 位就可以,而无符号整数的数量有 2^32 个,那么其实只需要 2^32 / 8 / 1024 / 1024 = 512M,内存消耗大大减少,很明显这是OK的,我们一下就看到了可能,也有了位图的一个概念:就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的

三、位图的应用

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

其实我们之前都讲过,大家也可以自行回顾一下

四、位图的使用

  有三种定义方式,分别如下:

  方式一:构造一个 16 位的位图,所有位都初始化为 0

bitset<16> bs1; // 0000000000000000

  方式二:构造一个 16位 的位图,根据所给值初始化位图的前 n 位

bitset<16> bs2(0xfa5); // 0000111110100101

  方式三:构造一个 16位 的位图,根据字符串中的 0 / 1 序列初始化位图的前 n 位

bitset<16> bs3(string("10111001")); // 0000000010111001

  常用函数如下:

成员函数功能
set设置指定位或所有位
reset清空指定位或所有位
flip反转指定位或所有位
test获取指定位的状态
count获取被设置位的个数
size获取可以容纳的位的个数
any如果有任何一个位被设置则返回true
none如果没有位被设置则返回true
all如果所有位都被设置则返回true
#include <iostream>
#include <bitset>
using namespace std;int main()
{bitset<8> bs;bs.set(2); // 设置第2位bs.set(4); // 设置第4位cout << bs << endl; // 00010100bs.flip(); // 反转所有位cout << bs << endl; //11101011cout << bs.count() << endl; // 6cout << bs.test(3) << endl; // 1bs.reset(0); // 清空第0位cout << bs << endl; // 11101010bs.flip(7); // 反转第7位cout << bs << endl; // 01101010cout << bs.size() << endl; // 8cout << bs.any() << endl; // 1bs.reset(); // 清空所有位cout << bs.none() << endl; // 1bs.set();   // 设置所有位cout << bs.all() << endl; // 1return 0;
}

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

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

#include <iostream>
#include <bitset>using namespace std;int main()
{bitset<8> bs;cin >> bs; // 10110cout << bs << endl; // 00010110return 0;
}
#include <iostream>
#include <string>
#include <bitset>
using namespace std;int main()
{bitset<8> bs1(string("10101010"));bitset<8> bs2(string("10101010"));bs1 >>= 1;cout << bs1 << endl; // 01010101bs2 |= bs1;cout << bs2 << endl; // 11111111return 0;
}
#include <iostream>
#include <string>
#include <bitset>
using namespace std;int main()
{bitset<8> bs1(string("10101010"));bitset<8> bs2(string("01010101"));cout << (bs1 & bs2) << endl; // 00000000cout << (bs1 | bs2) << endl; // 11111111cout << (bs1 ^ bs2) << endl; // 11111111return 0;
}
#include <iostream>
#include <string>
#include <bitset>
using namespace std;int main()
{bitset<8> bs(string("00110101"));cout << bs[0] << endl; // 1bs[0] = 0;cout << bs << endl; // 00110100return 0;
}

五、模拟实现

接口总览

namespace HQ
{//模拟实现位图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();private:vector<int> _bits; //位图};
}

  我们把 bitset 放到我们自己的命名空间里面,防止与标准库里面的 bitset 发生冲突

构造函数

  我们传给模板的参数 N 表示的是比特位数,创建的时候要开出来并且要全部设置为0,而一个整数一共有32个比特位,所以我们需要 N / 32 个整数,又因为 / 是向下取整,所以我们加个 1 保证比特位数量足够

在这里插入图片描述

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

set、reset、flip、test

  很明显这些都要位运算了,我们可以先想想怎么找到指定的一个比特位,显然得先定位到一个 int , i = N / 32 ,接下来就该思考怎么定位到这个int 32 个比特位中的哪一个,显然是 N % 32

//设置位
void set(size_t pos)
{assert(pos < N);// 算出 pos 映射的位在第 i 个整数的第 j 个位int i = pos / 32;int j = pos % 32;_bits[i] |= (1 << j); // 将该位设置为 1(不影响其他位)
}// 清空位
void reset(size_t pos)
{assert(pos < N);// 算出 pos 映射的位在第 i 个整数的第 j 个位int i = pos / 32;int j = pos % 32;_bits[i] &= (~(1 << j)); // 将该位设置为 0(不影响其他位)
}// 反转位
void flip(size_t pos)
{assert(pos < N);// 算出 pos 映射的位在第 i 个整数的第 j 个位int i = pos / 32;int j = pos % 32;_bits[i] ^= (1 << j); // 将该进行反转(不影响其他位)
}// 获取位的状态
bool test(size_t pos)
{assert(pos < N);//算出 pos 映射的位在第 i 个整数的第 j 个位int i = pos / 32;int j = pos % 32;return _bits[i] & (1 << j)
}

size、count

  很显然 size() 直接返回位数即可,而 count() 本质上也就是遍历二进制中1的个数,其实应该也早就接触过了,将 n 与 n - 1 进行与运算得到新的n就是去除二进制中最后一个1的过程,这个时候我们再判断一下 n 是否为 0 ,如果不是继续循环,无非就是加个计数器的问题

// 获取可以容纳的位的个数
size_t size()
{return N;
}// 获取被设置位的个数
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,显然不造成影响,所以any也就很自然而然的写出来了,none 很显然就是 any 取反,而 all 函数就要考虑之前 N / 32 + 1的 +1 带来的问题了
在这里插入图片描述

//判断位图中是否有位被设置
bool any()
{// 遍历每个整数for (auto e : _bits){if (e) // 该整数中有位被设置return true;}return false; //全部整数都是 0 ,则没有位被设置过
}// 判断位图中是否全部位都没有被设置
bool none()
{return !any();
}//判断位图中是否全部位都被设置
bool all()
{size_t n = _bits.size();  // 整数个数size_t full = N / 32;     // 完整使用的整数个数// 检查前full个整数(应该全为1)for (size_t i = 0; i < full; i++){if (_bits[i] != ~0u)  // 不等于全1return false;}// 检查最后一个整数size_t rem = N % 32;      // 剩余位数if (rem == 0){// 没有剩余位,最后一个整数也应该全1return _bits[full] == ~0u;}else{// 只检查前rem位uint32_t mask = (1u << rem) - 1;return (_bits[full] & mask) == mask;}
}

总结

  好像不算太难,接下来我们要学的布隆过滤器才算是一个重点!


文章转载自:

http://ksNiZeuD.rtqyy.cn
http://akef18GH.rtqyy.cn
http://u3QcHS39.rtqyy.cn
http://BNsPHEvm.rtqyy.cn
http://OadhPgRI.rtqyy.cn
http://1vtBHNfp.rtqyy.cn
http://Xc89OIbf.rtqyy.cn
http://XwBSOyoA.rtqyy.cn
http://j6QQ4sSs.rtqyy.cn
http://uJnNAoEj.rtqyy.cn
http://a2D40mwp.rtqyy.cn
http://7Y0fl6p5.rtqyy.cn
http://XYHwz5KI.rtqyy.cn
http://mCdT9VEe.rtqyy.cn
http://SdmUAiMC.rtqyy.cn
http://2wVmXpfB.rtqyy.cn
http://6HYMbeqD.rtqyy.cn
http://3OQVeZOm.rtqyy.cn
http://slqFjBWX.rtqyy.cn
http://VUyovnCh.rtqyy.cn
http://R3hkvMq1.rtqyy.cn
http://wJCoqeaF.rtqyy.cn
http://NbprqNGI.rtqyy.cn
http://tfEUQZpx.rtqyy.cn
http://8b8LmUCi.rtqyy.cn
http://OlDQbjgy.rtqyy.cn
http://2Z460s8Y.rtqyy.cn
http://vRYXzx5Y.rtqyy.cn
http://tO1Tmu3f.rtqyy.cn
http://wTc5Fw5E.rtqyy.cn
http://www.dtcms.com/a/374062.html

相关文章:

  • 链表 (C/C++)
  • WinEdt编译tex文件失败解决办法
  • C语言第12讲
  • commons-email
  • (堆)347. 前 K 个高频元素
  • GitHub Release Monitor部署指南:实时追踪开源项目更新与自动通知
  • 重新定义音频编程:SoundFlow如何以模块化设计革新.NET音频开发生态
  • SQL 注入与防御-第八章:代码层防御
  • Miniflux 安全升级:绑定域名并开启 HTTPS
  • 标准 HTTP 状态码详解
  • STM32开发(创建工程)
  • MFC 图形设备接口详解:小白从入门到掌握
  • APM32芯得 EP.34 | 告别I2C“假死”——APM32F103硬件IIC防锁死设计
  • n8n入门
  • 静态住宅IP的特点
  • 数智之光燃盛景 共同富裕创丰饶
  • colmap+pycolmap带cuda编译
  • Nano-Bananary 搭建 使用 nano banana
  • 前端性能监控与优化:从 Lighthouse 到 APM
  • 浅聊一下微服务的网关模块
  • 硬件开发2-ARM基本概要
  • C++11第二弹(右值引用与移动语义)
  • 数电实验二连线
  • MQTT+WebSocket工业协议实战:高并发SCADA系统通信架构设计
  • Claude-Flow AI协同开发:基础入门之 AI编排
  • Android面试指南(七)
  • 西嘎嘎学习 - C++修饰符类型 - Day 5
  • 明远智睿RK3568核心板:199元解锁多行业智能新可能
  • LeetCode算法日记 - Day 36: 基本计算器II、字符串解码
  • linux系统address already in use问题解决