来源:
在Jon Bentley的Programming Pearls一书中,第一列介绍了排序问题。当我们更多地了解问题并清楚地定义它的约束时,解决方案从使用磁盘的合并排序(Merge Sort)转换为更为有效的位图排序(Bitmap Sort)。
所谓的Bitmap Sort 就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。
该算法使用位图(或位向量)来表示一组有限的不同整数。例如,如果我们有一个0-5的整数范围,我们可以使用一个6位数组来表示它,例如:
[2,3,5]变为0 0 1 1 0 1
[1,3,4]变为0 1 0 1 1 0
再看个简单的问题:
假如给你20亿个非负数的int型整数,然后再给你一个非负数的int型整数 t ,让你判断t是否存在于这20亿数中,你会怎么做呢?
有人可能会用一个int数组,然后把20亿个数给存进去,然后再循环遍历一下就可以了。
想一下,这样的话,时间复杂度是O(n),所需要的内存空间
4byte * 20亿,一共需要80亿个字节,
大概需要8GB的内存空间,显然有些计算机的内存一次是加载不了这么这么多的数据的。
这个时候,Jon Bentley提出了他的思路:
我们在使用byte,int,short,long等这些数据类型在存储数据的时候,他们最小的都要占用一个字节的内存,也就是8个bit,也就是说,最小的操作单位是8个bit。根本就没有可以一个一个bit位操作的数据类型啊。
在C/C++的bitMaP实现中,它采用的是用一个int数据来进行存储的。一个int占用84个字节,即32bit,所以一个int可以存储32个数。例如 arr 是一个int类型的数组,则 arr[0]可以存 0 ~ 31,arr[1]可以存32 ~63,以此类推。
32位机器上,一个整形,比如 int a; 在内存中占32bit,可以用对应的32个bit位来表示十进制的0-31个数,bitmap算法利用这种思想处理大量数据的排序与查询。
优点:
缺点:
示例演示:
我们都知道,一个二进制位,有0和1两种状态,所以说,其实我们是可以用一个二进制位来代表一个int型的数是否存在的。例如对于1,3,5,7这四个数,如果存在的话,则可以这样表示:
首先第一个元素是1,那么就把1对应的位置为1(可以这样操作 p+(i/8)|(0x01<<(i%8)) 当然了这里的操作涉及到big-endian和Little-endian的情况,这里为Big-endian:最高位字节存储在最低的内存地址处),因为是从零开始的,所以要把第五位置为一(如下图):
1代表这个数存在,0代表不存在。例如表中01010101代表1,3,5,7存在,0,2,4,6不存在。
那如果8,10,14也存在怎么存呢?如图,8,10,14我们可以存在第二个字节里
附加:字节顺序,又称端序或尾序(英语:Endianness)
字节的排列方式有两个通用规则。例如,一个多位的整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)在最高有效字节的前面,则称大端序;反之则称小端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。
小端序(英:little-endian)情况
思想比较简单,关键是十进制和二进制bit位需要一个 map 映射表,把10进制映射到bit位上。
假设需要排序或者查找的总数N=10000000,那么我们需要申请的内存空间为 int a[ N/32 + 1 ].其中a[0]在内存中占32位,依此类推:
bitmap表为:
a[0]--------->0-31 (一个int型占4个字节,共占32位。)
a[1]--------->32-63
a[2]--------->64-95
a[3]--------->96-127
..........
那么十进制数如何转换为对应的bit位,下面介绍用位移将十进制数转换为对应的bit位。
申请一个int一维数组,那么可以当作为列为32位的二维数组,
| 32位 |
int a[0] |0000000000000000000000000000000000000|
int a[1] |0000000000000000000000000000000000000|
………………
int a[N] |0000000000000000000000000000000000000|
例如十进制0,对应在a[0]所占的bit为中的第一位: 00000000000000000000000000000001
0-31:对应在a[0]中
i=0 00000000000000000000000000000000 (i:实际对应的十进制)
temp=0 00000000000000000000000000000000 (temp=i%32,n=i/32,将a[n]中answer二进制的第temp位"置1")
i =1 00000000000000000000000000000001
temp=1 00000000000000000000000000000001
answer=2 00000000000000000000000000000010
i =2 00000000000000000000000000000010
temp=2 00000000000000000000000000000010
answer=4 00000000000000000000000000000100
i =30 00000000000000000000000000011110
temp=31 00000000000000000000000000011110
i =31 00000000000000000000000000011111
temp=31 00000000000000000000000000011111
answer=-2147483648 10000000000000000000000000000000
32-63:对应在a[1]中
i =32 00000000000000000000000000100000
answer=1 00000000000000000000000000000001
i =33 00000000000000000000000000100001
temp=1 00000000000000000000000000000001
answer=2 00000000000000000000000000000010
i =34 00000000000000000000000000100010
temp=2 00000000000000000000000000000010
answer=4 00000000000000000000000000000100
i =61 00000000000000000000000000111101
temp=29 00000000000000000000000000011101
answer=536870912 01000000000000000000000000000000
i =62 00000000000000000000000000111110
temp=30 00000000000000000000000000011110
answer=1073741824 01000000000000000000000000000000
i =63 00000000000000000000000000111111
temp=31 00000000000000000000000000011111
answer=-2147483648 10000000000000000000000000000000
浅析上面的对应表,分三步:
(1) 求十进制数 0-N 对应的在数组 a 中的下标(求a[N]中的N的大小)
index_loc = N / 32即可,index_loc即为n对应的数组下标。例如n = 76, 则loc = 76 / 32 = 2,因此76在a[2]中。
(i/32用按位运算表示i>>5)
(2) 求十进制数0-N对应的bit位
bit_loc = N % 32即可,例如 n = 76, bit_loc = 76 % 32 = 12。 (i%32 用按位运算表示 i & 0x1F)
(3) 利用移位0-31使得对应的32bit位为1
找到对应0-31的数为M, 左移M位:即2^M. 然后置1.
将他们合起来就是(注意“|=”.):
a[i>>5] |= (1<<(i & 0x1F)); //等价于a[i/32] |= (1<<(i%32));
附上代码
#define INT_BITS sizeof(int)
#define SHIFT 5 // 2^5=32
#define MASK 0x1f // 2^5=32
#define MAX 1024*1024*1024 //max number
int bitmap[MAX / INT_BITS];
//设置第i位
void set(int i) {
bitmap[i >> SHIFT] |= 1 << (i & MASK);
}
//获取第i位,测试所在的bit为是否为1
int test(int i) {
return bitmap[i >> SHIFT] & (1 << (i & MASK));
}
//清除第i位
int clear(int i) {
return bitmap[i >> SHIFT] & ~(1 << (i & MASK));
}
//初始化所有的bit位为0
void clr(int i) {
a[i>>SHIFT] &= ~(1<<(i & MASK));
}
由此我们计算10000000个bit占用的空间:
1byte = 8bit
1kb = 1024byte
1mb = 1024kb
占用的空间为:10000000/8/1024/1024mb。
大概为1mb多一些。
1、在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数
解法一:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^32 * 2 bit=1 GB内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
解法二:也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。”
实现:
// TestWin32.cpp : Defines the entry point for the console application.
//代码来自 https://blog.csdn.net/hguisu/article/details/7880288#commentBox
#include "stdafx.h"
#include
//用char数组存储2-Bitmap,不用考虑大小端内存的问题
unsigned char flags[1000]; //数组大小自定义
unsigned get_val(int idx) {
// | 8 bit |
// |00 00 00 00| //映射3 2 1 0
// |00 00 00 00| //表示7 6 5 4
// ……
// |00 00 00 00|
int i = idx/4; //一个char 表示4个数,
int j = idx%4;
unsigned ret = (flags[i]&(0x3<<(2*j)))>>(2*j);
//0x3是0011 j的范围为0-3,因此0x3<<(2*j)范围为00000011到11000000 如idx=7 i=1 ,j=3 那么flags[1]&11000000, 得到的是|00 00 00 00|
//表示7 6 5 4
return ret;
}
unsigned set_val(int idx, unsigned int val) {
int i = idx/4;
int j = idx%4;
unsigned tmp = (flags[i]&~((0x3<<(2*j))&0xff)) | (((val%4)<<(2*j))&0xff);
flags[i] = tmp;
return 0;
}
unsigned add_one(int idx)
{
if (get_val(idx)>=2) { //这一位置上已经出现过了??
return 1;
} else {
set_val(idx, get_val(idx)+1);
return 0;
}
}
//只测试非负数的情况;
//假如考虑负数的话,需增加一个2-Bitmap数组.
int a[]={1, 3, 5, 7, 9, 1, 3, 5, 7, 1, 3, 5,1, 3, 1,10,2,4,6,8,0};
int main() {
int i;
memset(flags, 0, sizeof(flags));
printf("原数组为:");
for(i=0;i < sizeof(a)/sizeof(int); ++i) {
printf("%d ", a[i]);
add_one(a[i]);
}
printf("\r\n");
printf("只出现过一次的数:");
for(i=0;i < 100; ++i) {
if(get_val(i) == 1)
printf("%d ", i);
}
printf("\r\n");
return 0;
}
2、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?
解法一:可以用位图/Bitmap的方法,申请512M的内存,一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。
Bloom filter可以看做是对bit-map的扩展
C++的STL中有bitmap类,它提供了很多方法,详见:http://www.cplusplus.com/reference/stl/bitset/
1.枚举:(1)全组合(2)哈米尔顿距离
2.搜索
3.压缩
借鉴资料:
https://mp.weixin.qq.com/s/SVePRbrgyLck8UnLVRVJdA
https://mp.weixin.qq.com/s/pCD1UaNunHfVedkS9Lyp3Q
https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/06.07.html
http://letsalgorithm.blogspot.com/2012/02/bitmap-sort.html
https://www.cnblogs.com/chanshuyi/p/5287825.html
https://blog.csdn.net/hguisu/article/details/7880288
https://www.kancloud.cn/kancloud/the-art-of-programming/41618
https://blog.csdn.net/hustyangju/article/details/47022791