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

比特之绘:位图的二进制诗学

一、位图概念

就是用每一位比特位来存放某种状态,1或0

适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在

下面我用一个场景来进一步解释

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

  • 一:遍历,时间复杂度O(N),如果多次查询,需要多次遍历40亿个无符号整数,消耗大
  • 二:排序O(N * logN),用二分O(logn)进行查找,40亿个无符号整数,在32位环境下,一个size_t 整数的大小是4字节(64位环境下,一个size_t 的大小是8字节),那么我们进行推算一下1G大约等于多少字节——>

1G == 1024MB (2^10KB) == 1024 * 1024KB (2^20KB) == 1024 * 1024 *1024Byte(2^30Byte)
——> 那么也就是1G大约是10亿字节(Byte)

  • 那么40亿个size_t 整数,一个整数是四个字节,那么40亿个无符号整数就是160亿个字节(Byte),由于1G == 10亿字节,所以40亿个无符号整数约等于16G,要进行排序需要在内存才能进行排序,内存的空间不足以满足这里的16G的需求进行排序,所以方法二也不行

————> 位图 : 位图在这里的思想是就是

你数字是几他就找,第多少个数字个数的比特位 ,然后让这个位置上变成1或者0,表示这个数字存在或不存在,如果判断105是否在,那么他就会找到第105个比特位,然后通过位运算,让这个位置上变成1或者是0 ,因为 进行判断size_t 整数是在或者不在,刚好是两种状态,那么我们就可以使用二进制比特位0, 1来表示这两种状态,如果比特位是1,那么表示存在,如果比特位是0,那么表示不存在,将这40亿个不重复的无符号整数放到位图中表示状态之后,进行查询给出的无符号数就可以使用O(1)的时间复杂度快速的查询判断是否存在 


二、位图的逻辑

我们通过存很多比特位,我们通过比特位上的零或一来判断该数字存不存在,由于没有大小为一的类型,所以我们只能存int的类型或者是char类型,这里选择存int类型 ,一个int是四个字节,一个字节是八个比特位 , 所以一个int是32个比特位 ,存一个int代表32个比特位 ,我们只需要将传过来的值除以32表示,看他是第几个32,然后再%上32,看他在该最后一个32内排到第几位,这样我就把那个数字的值转化为了第多少个比特位

一个整形占四个字节及32个比特位,所以一个整形可以表示32个数字存在或不存在

友友们  ,可能会好奇,为什么比特位是这样存的

这就要看,1在电脑中的二进制是怎样存的?这里就要分小端机和大端机 ,小端机是反着存的,

(为什么有小端机和大端机 ,这就类似于古代的候的百家争鸣一样,大家不是统一好的,都是各自有想法,然后各自去做,各自做,看谁做的更好而已)

取出int x=1的x的地址来查看它的内存

这是他的底层存储 ,但是我们的逻辑是这样的 ,00000000000000....001,  友友们 可能在这里想到,如果按他这样的底层存储逻辑,那我们一通过左移,那怎么移到,像我们逻辑存储的那样对应位置上去呢 ,实际上,1在内存中怎么存是,是不重要的,编译器会把它的移动修改到对应上我们逻辑中的移动 , 00 00 00 01,是大端机  ,01 00 00 00 是小端机 (vs) 

一定要记住,左移右移不是改变方向方向,而是改变数据位置的高低 ,左移就是往高位移,右移就是往低位移


三、位图的实现

template<size_t N>
class bitset
{
public:bitset(){_a.resize(N / 32 + 1);}// x映射的那个标记成1void set(size_t x){size_t i = x / 32;size_t j = x % 32;_a[i] |= (1 << j);}// x映射的那个标记成0void reset(size_t x)//{size_t i = x / 32;size_t j = x % 32;_a[i] &= (~(1 << j));}bool test(size_t x)//找到该数字存不存在 -- 1或0{size_t i = x / 32;size_t j = x % 32;return _a[i] & (1 << j);}
private:vector<int> _a;
};
  • 注意vector,他要开空间 ,因为我们只是定义了一个空的vector,他的size()是零,他的capacity()是零 ,我们需要用resize给他开存储数据的空间 ,就像定义一个数组,在后面要加上括号内的数字一样
  • 那我们要那我们要给他开,多少空间呢?一个整形是32个比特位,我们要存多少个数据,我们只需要用那个数据,除以32,为了确保有足够,再加上一个1,这就是我们要开的空间
  • 上面的问题是给40亿个不重复的无符号整数,我们不能说只开40亿个数 ,因为位图在这里的逻辑是它的数字是多少,假如是100,我就找到这个,第100个比特位上,然后让他等于1,这里有40亿个不重复的数字,有些数字可能很大,可能超过40亿,所以说我们要开一个范围 ,而size_t 的范围是无符号数的范围是42亿9千万,无符号数在32位下的大小是4个自己,那么42亿9千万个无符号数,当我们使用位图去开空间的时候,就要开42亿9千万个比特位,那么就是42亿9千万比特位就是大约5亿字节,1G大约是10亿字节,那么也就是大约0.5G,也就是500MB,可见使用位图还是很节省空间的
  • 如何对位图开42亿9千万比特位的空间呢 

way 1: 

bit::bitset<-1> bs1;//负一传给无符号,他的二进制就是全部都是1,此时就是最大值了

way2:

bit::bitset<UINT32_MAX> bs2;

  • void set(size_t x) 将映射的那个标记成1
  •  void reset(size_t x) 将映射的那个标记成0
  •  test(size_t x) 找到该数字存不存在 ——> 1或0    

    test一下: 

#include <iostream>
using namespace std;#include "BitSet.h"int main()
{bit::bitset<1000> bs;bs.set(1);bs.set(10);bs.set(100);cout << bs.test(1) << endl;cout << bs.test(10) << endl;cout << bs.test(100) << endl << endl;bs.reset(10);cout << bs.test(1) << endl;cout << bs.test(10) << endl;cout << bs.test(100) << endl << endl;return 0;
}

四、位图的应用

  •  快速查找某个数据是否在一个集合中
  •  排序 + 去重
  •  求两个集合的交集、并集等
  •  操作系统中磁盘块标记

给定100亿个整数,设计算法找到只出现一次的整数?

  1. 由于题目的需求是找到只出现一次的整数,那么我们就需要进行统计整数的次数,这些整数有出现0次的,有只出现一次的,有出现两次及以上的整数,那么我们并不需要真的统计出所有整数出现的次数,而是统计出现0次,出现1次,出现2次进行统计,其中出现2次代表出现了两次及以上的次数
  2. 我们可以使用两个位图的同一个位置的两个比特位去控制,因为两个比特位可以表示00,01,10,11,对应到二进制位其中00表示出现0次,01表示出现1次,10表示出现了两次(这道题目中特殊应用,使用10表示出现了两次及以上,即当一个数字已经出现了两次之后,再出现一次我们仍然使用两次进行统计,因为这里的两次表示出现了两次即以上),11在这道题目中不需要进行设置,因为00,01,10已经可以满足我们的需求了
  3. 我们复用先前写的bitset ,用两个 bitset 去封装一个类twobitset 这样有两个位图进行控制,第一个位图控制第一位,第二个位图控制第二位
template<size_t N>
class twobitset
{
public:void set(size_t x){if (!(set1.test(x)) && !(set2.test(x)))//是00的情况{set1.set(x);//变成10 ——> 1个}else if ((set1.test(x)) && !(set2.test(x)))//是10的情况{set2.set(x);//变成11 ——>2个}else if ((set1.test(x)) && (set2.test(x)))//是11的情况{set1.reset(x);//变成01 ——>2个以上}}void only_once(size_t x){if (set1.test(x) && !(set2.test(x)))//10{cout << x << " ";}}bitset<N> set1;bitset<N> set2;
};
  • void only_once(size_t x) : 如果这个数字在set 1和set 2中,遍历完之后只是 10 的状态,那说明这个数字只出现了一次

测试一下: 

bit::bitset<1000> bs;
bit::twobitset<1000> tbs;
int arr[] = { 1,9,9,8,23,12,7,2,3,7,8,9,9,8,7,2,3,4,5,6,6,5,4,3,2 };
//给定100亿个整数,设计算法找到只出现一次的整数?
for (size_t e : arr)
{tbs.set(e);
}for (size_t e : arr)
{tbs.only_once(e);
}
cout << endl;


给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

  • 再用位图遍历数字时,相当于是起到了去重的效果,因为就算有相同的数字,那么对于那个数字上的比特位依旧还是1
  • 我们用位图bs1和bs2分别去遍历,这两个文件(这里用数组代替),如果在bs1和bs2中,同一个比特位都显示为一,说明这个数字是ta们的交集 
int arr1[] = { 1,9,9,2,3,4,8,23,12,7,2,3,3,4,8,23,12,6,5,4,37,8,9,9,8,7,5,6,2 };
int arr2[] = { 1,9,9,8,7,5,6,2 };
bit::bitset<1000> bs1;
bit::bitset<1000> bs2;for (size_t e : arr1)//这里的位图相当于取到了去重的效果
{bs1.set(e);
}for (size_t e : arr2)
{bs2.set(e);
}for (size_t i = 0; i < 1000; i++)
{if (bs1.test(i) && bs2.test(i))cout << i << " ";
}


位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

  • 这里的,不超过两次就分为0次,一次和两次
  • 依旧用两个位图 ,如果两个位图 在同一个比特位上,两个比特位分别为00  ,则表示零次,01则表示一次,11则表示两次,10则表示两次,则只用检测前三种状态即可
int arr3[] = { 1,9,9,2,3,4,8,23,12,7,2,3,3,4,8,23,12,6,5,4,37,8,9,9,8,7,5,6,2 };bit::twobitset<1000> tbs;for (size_t e : arr3)
{tbs.set(e);
}for (size_t e : arr3)
{if (!(tbs.set1.test(e)) && !(tbs.set2.test(e)))//都为零-00{cout << e << " ";}else if ((tbs.set1.test(e)) && (tbs.set2.test(e)))//11{cout << e << " ";}else if ((tbs.set1.test(e)) && !(tbs.set2.test(e)))//10{cout << e << " ";}
}


五、源代码

test.cpp

#include<iostream>
using namespace std;
#include"BitSet.h"int main()
{bit::bitset<1000> bs;bit::twobitset<1000> tbs;int arr[] = { 1,9,9,8,23,12,7,2,3,7,8,9,9,8,7,2,3,4,5,6,6,5,4,3,2 };//给定100亿个整数,设计算法找到只出现一次的整数?for (size_t e : arr){tbs.set(e);}for (size_t e : arr){tbs.only_once(e);}cout << endl;bit::bitset<0xffffffff> bs2;//给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?int arr1[] = { 1,9,9,2,3,4,8,23,12,7,2,3,3,4,8,23,12,6,5,4,37,8,9,9,8,7,5,6,2 };int arr2[] = { 1,9,9,8,7,5,6,2 };bit::bitset<1000> bs1;bit::bitset<1000> bs2;for (size_t e : arr1)//这里的位图相当于取到了去重的效果{bs1.set(e);}for (size_t e : arr2){bs2.set(e);}for (size_t i = 0; i < 1000; i++){if (bs1.test(i) && bs2.test(i))cout << i << " ";}//位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数int arr3[] = { 1,9,9,2,3,4,8,23,12,7,2,3,3,4,8,23,12,6,5,4,37,8,9,9,8,7,5,6,2 };bit::twobitset<1000> tbs;for (size_t e : arr3){tbs.set(e);}for (size_t e : arr3){if (!(tbs.set1.test(e)) && !(tbs.set2.test(e)))//都为零-00{cout << e << " ";}else if ((tbs.set1.test(e)) && (tbs.set2.test(e)))//11{cout << e << " ";}else if ((tbs.set1.test(e)) && !(tbs.set2.test(e)))//10{cout << e << " ";}}return 0;
}

bitset 和 twobitset

#pragma once
#include<vector>namespace bit
{template<size_t N>class bitset{public:bitset(){_a.resize(N / 32 + 1);}// x映射的那个标记成1void set(size_t x){size_t i = x / 32;size_t j = x % 32;_a[i] |= (1 << j);}// x映射的那个标记成0void reset(size_t x)//{size_t i = x / 32;size_t j = x % 32;_a[i] &= (~(1 << j));}bool test(size_t x)//找到该数字存不存在 -- 1或0{size_t i = x / 32;size_t j = x % 32;return _a[i] & (1 << j);}private:vector<int> _a;};template<size_t N>class twobitset{public:void set(size_t x){if (!(set1.test(x)) && !(set2.test(x)))//是00的情况{set1.set(x);//变成10 ——> 1个}else if ((set1.test(x)) && !(set2.test(x)))//是10的情况{set2.set(x);//变成11 ——>2个}else if ((set1.test(x)) && (set2.test(x)))//是11的情况{set1.reset(x);//变成01 ——>2个以上}}void only_once(size_t x){if (set1.test(x) && !(set2.test(x)))//10{cout << x << " ";}}bitset<N> set1;bitset<N> set2;};
}

http://www.dtcms.com/a/415413.html

相关文章:

  • 【K8s-Day 32】StatefulSet 深度解析:为你的数据库和有状态应用保驾护航
  • 优质的营销网站建设广告公司取名
  • Webpack5 第四节
  • 设计网站公司力荐亿企邦松江新城投资建设发展有限公司网站
  • 家用电器:从解放双手到智能生活的变革者
  • 上海网站建设大概多少钱WordPress允许用户修改评论
  • 如何给网站做seo优化用网站做宣传的方案
  • 使用神经网络预测天气
  • 青海省建设工程信息网站最近十大新闻
  • 建设银行网站显示404企业活动网站创意案例
  • XAMPP下载安装教程(附下载链接,图文并茂)
  • 杭州网站建设浙江搜搜网站收录
  • Redis-分布式锁-redission原理
  • 博州住房和城乡建设局网站wordpress搭建官网
  • 做报废厂房网站怎么做网站架设的结构
  • 海口h5建站西地那非片能延时多久有副作用吗
  • 从知乎403拦截到成功采集:网页抓取实战
  • wordpress建立购物网站seo综合查询
  • 极简 Go 语言教程:从 Java 开发者视角 3 小时入门实战
  • 济宁网站建设第一品牌深圳十大品牌策划公司
  • 破解大语言模型的无失真水印
  • Android开发-Fragment
  • 等额本息年利率反推方法
  • 电商网站建设需要开原网站开发
  • 网站推广服务合同中国建筑集团有限公司电话
  • 全国金融许可证失控情况数据(邮政储蓄网点 / 财务公司等)2007.7-2025.7
  • 基于STM32与influxDB的电力监控系统-5
  • 太原做app网站建设推广普通话奋进新征程宣传语
  • 【Day 65】Linux-ELK
  • 怎么免费建立自己的网站平台站长之家