给40亿个不重复的无符号整数,没拍过序。给定一个无符号整数,如何可以高效的判断是否存在这些数据中。
直接的想法是,我们将这些的无符号整数存储到内存中,然后用给定的数进行一一比较。
我们分析一下,一个无符号整数是4个字节,40亿* 4个字节,10亿个字节大概就是4个G,40亿个无符号整数就是16G
计算机内存一般没这么大,就算有这么大,也要分给操作系统什么的吧,直接存储整形不好实现;
再说,这对比一遍,如果不存在,那么要比较40亿多次,效率太低了
现在,我们可以用位图---BitMap来解决这一问题。即节省空间,又可以快速的解决这个问题
位图的概念
位图是用数组的每一个元素的每一个二进制位来表示数据的存在与否;
0表示不存在,1则表示存在
位图的优势
位图可以快速的判断一个存储的关键字是否存在
并且40亿个整数,一个整数只占一个位,空间占用率比之前少了31倍,只需要大概500M左右的样子。
位图的剖析
步骤1 选用一种我们已学过的数据结构
位图需要的是二进制位,通过用二进制位的0,1来表示一个数是否存在
我们用vector来存储这些数,可以将vector放入任意基本类型;
步骤2 找到对应的位
这次我们放入size_t 无符号整形,有32个比特位。也就是说,一个vector的一个对应下标可以表示32个数是否存在;
vector<size_t> _v;//存储各个数的状态 size_t _size;//标志有多少个数存入现在给我们一个数,我们用它除以32,得到的商就是对应的下标,得到的余数就是该对应下标中的第几个比特位;
步骤3 考虑如何将该位置为1
我们知道二进制的或运算,可以将一个位置为1;
置位的代码
//放入,将num的所在位置为1 void Set(size_t num) { //index用来判断在vector的第几个元素中 //value表示在该元素中从低位向高位数具体的哪一位 //index用右移5位,相当于除32,用移位运算效率高 int index = num >> 5; int value = num % 32; //利用或运算,将该位置为1 _v[index] |= 1 << (value-1); _size++; }
步骤4 如何重置,将一个数在位图中抹去
还是用位运算,这次用异或;
相同为0,相异为1;
//移出,将num的所在位置为0 void Reset(size_t num) { //index用来判断在vector的第几个元素中 //value表示在该元素中从低位向高位数具体的哪一位 //index用右移5位,相当于除32,用移位运算效率高 int index = num >> 5; int value = num % 32; //利用异或运算,将该位置为0 _v[index] ^= 1 << (value-1); _size--; }
步骤5 判断一个数是否存在于位图中
依旧位运算,用与,都为1则为1,否则为0
//用来判断一个数是否已经存入位图中 bool Find(size_t num) { int index = num >> 5; int value = num % 32; //判断该位是否为1 return (_v[index] >> (value-1)) & 1; }
源代码
#include<vector> //定义位图 class BitMap { public: //位图的构造函数 BitMap(size_t size = 1024) :_size(0) { //一个无符号整形(size_t)可以有32个位分别表示32个数 _v.resize(size / 32 + 1); } //放入,将num的所在位置为1 void Set(size_t num) { //index用来判断在vector的第几个元素中 //value表示在该元素中从低位向高位数具体的哪一位 //index用右移5位,相当于除32,用移位运算效率高 int index = num >> 5; int value = num % 32; //利用或运算,将该位置为1 _v[index] |= 1 << (value-1); _size++; } //移出,将num的所在位置为0 void Reset(size_t num) { //index用来判断在vector的第几个元素中 //value表示在该元素中从低位向高位数具体的哪一位 //index用右移5位,相当于除32,用移位运算效率高 int index = num >> 5; int value = num % 32; //利用异或运算,将该位置为0 _v[index] ^= 1 << (value-1); _size--; } //用来判断一个数是否已经存入位图中 bool Find(size_t num) { int index = num >> 5; int value = num % 32; //判断该位是否为1 return (_v[index] >> (value-1)) & 1; } protected: vector<size_t> _v; size_t _size; };
位图的一些应用
应用1 利用位图进行排序
将这些数遍历一遍,放入位图的对应位置上。然后从低位到高位遍历一遍每一个比特位;
缺陷是对于负数,要转化成无符号的整数,输出的时候再减去最小值即可
应用2 判断是否出现重复
遍历这些数,如果不存在则放入位图中,否则则找到了重复的元素。