一、前提引入
思考如何实现下面的题目
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。【腾讯】
一个整型数据int为4个字节,计算得 ,大约需要16G的存储空间。如果采用遍历判断的话,时间复杂度为O(N)较高,并且不利于多次反复判断
如果采用二分查找,需要先排序再查找时间复杂度为O(logN)
由于只需要判断一个数是否存在,而无需存储数字本身。因此可以将用字节存储数据转换为用位标识,比特位为1则代表存在,如果比特位为0则代表不存在
字节最少的数据类型是char,所以采用vector存储char类型的数据。每个char包括一个字节,即8个比特位,每个比特位用于标识一个数字是否存在
因此将存储空间压缩为40亿/8=5亿个数据,也就是512MB的空间
二、位图
位图,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的
位图其实是用数组实现的,数组的每一个元素的每一个二进制位都可以表示一个数据在或者不在,0表示数据存在,1表示数据不存在。因为比特位只有两种状态,要不是0,要不就是1,所以位图其实就是一种直接定址法的哈希,只不过位图只能表示这个值在或者不在
作用
1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记
优点 :速度快节省空间
缺点:只能映射整型,其他类型比如浮点数、string等不能存储映射
三、程序实现
元素定位
位图是通过数组来实现的,因此首先需要定位所在的字节位置,然后在字节中定位相应的比特位置
通过以下代码来实现定位
int i = x / 8;//判断字节位置int j = x % 8;//判断字节中的比特位位置
元素设置
在已经知道元素应该放置的位置后,需要将已知元素所在位置的比特位值更改为1
_bits[i] |= (1 << j);//这里的左移是指向高位移动
采用或运算,将1左移j位,采用按位或赋值,将其赋值为1,代表该数字存在
元素移出
与元素设置思路类似
_bits[i] &= ~(1 << j);//这里的左移是指向高位移动
采用与运算,将1左移j位,取反为0
#pragma once
#include<iostream>
#include<vector>using std::vector;namespace my_bitset
{template<size_t N>//N为需要存储的位数量class bitset{public:bitset(){_bits.resize(N / 8 + 1, 0);//向上取整}void set(){int i = x / 8;//判断字节位置int j = x % 8;//判断字节中的比特位位置_bits[i] |= (1 << j);//这里的左移是指向高位移动}void reset(size_t x){int i = x / 8;//判断字节位置int j = x % 8;//判断字节中的比特位位置_bits[i] &= ~(1 << j);//这里的左移是指向高位移动}bool test(size_t x){int i = x / 8;//判断字节位置int j = x % 8;//判断字节中的比特位位置return _bits[i] &= (1 << j);}private:vector<char> _bits;};
}
namespace my_bitset_2
{template<size_t N>class twobitset{public://将每个数据用两个比特位标识,如果是00则代表没有出现,如果为01则代表出现一次,10则代表出现一次以上void set(size_t x){//00->01if (_bs1.test(x) == false && _bs2.test(x) == false){_bs2.set(x);}//01->10else if (_bs1.test(x) == false && _bs2.test(x) == true){_bs1.set(x);_bs2.reset(x);}}private:my_bitset::bitset<N> _bs1;my_bitset::bitset<N> _bs2;};
}