位图-BitMap

BitMap

字面意思解释为位图,准确翻译为基于位的映射

What is 基于位的映射?

  • 就是用一个bit位来标记某个元素对应的Value,而Key即是该元素;
    由于采用了bit为单位来存储数据,因此可以大大节省存储空间
举个例子,立马了解BitMap的基本思想
  • 32位机器上,对于一个整型数,比如int a = 1 在内存中占32 bit位,这是为了方便计算机的运算。但是对于某些应用场景而言,这属于一种巨大的浪费,因为我们可以用对应的32 bit位对应存储十进制的0 - 31个数,而这就是Bit-map的基本思想

基于上述基本思想,Bit-map算法在处理大量数据排序、查询以及去重;以及在用户群做交集和并集运算的时候也有极大的便利。



1. BitMap的应用
1.1 快速的排序

假设要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复),就可以采用Bit-map的方法来达到排序的目的。要表示 8个数,只需要8Bit(1 Bytes);
首先我们开辟1 Byte的空间,将这些空间的所有Bit位都置为0


然后遍历这5个元素,不考虑大小端,默认大端存储,将对应元素的对应位置为1,比如第一个元素为4,则将开辟的空间的第5位置为1(因为是从0开始的)

最后再遍历一遍Bit区域,将该位是1的位的编号输出(2,3,4,5,7),这样就达到了排序的目的,时间复杂度O(n)

  • 优点:
    运算效率高,不需要进行比较和移位;
    占用内存少,比如N = 10000000,只需占用内存为N/8 = 1250000Byte = 1.25M
  • 缺点:
    所有的数据不能重复。即不可对重复的数据进行排序和查找。
利用BitMap实现的排序

//定义每个Byte中有8个Bit位
#include <memory.h>
#define BYTESIZE 8
void SetBit(char *p, int posi)
{
    for(int i=0; i < (posi/BYTESIZE); i++)
    {
        p++;
    }
 
    *p = *p|(0x01<<(posi%BYTESIZE));//将该Bit位赋值1
    return;
}
 
void BitMapSortDemo()
{
    //为了简单起见,我们不考虑负数
    int num[] = {3,5,2,10,6,12,8,14,9};
 
    //BufferLen这个值是根据待排序的数据中最大值确定的
    //待排序中的最大值是14,因此只需要2个Bytes(16个Bit)
    //就可以了。
    const int BufferLen = 2;
    char *pBuffer = new char[BufferLen];
 
    //要将所有的Bit位置为0,否则结果不可预知。
    memset(pBuffer,0,BufferLen);
    for(int i=0;i<9;i++)
    {
        //首先将相应Bit位上置为1
        SetBit(pBuffer,num[i]);
    }
 
    //输出排序结果
    for(int i=0;i<BufferLen;i++)//每次处理一个字节(Byte)
    {
        for(int j=0;j<BYTESIZE;j++)//处理该字节中的每个Bit位
        {
            //判断该位上是否是1,进行输出,这里的判断比较笨。
            //首先得到该第j位的掩码(0x01<<j),将内存区中的
            //位和此掩码作与操作。最后判断掩码是否和处理后的
            //结果相同
            if((*pBuffer&(0x01<<j)) == (0x01<<j))
            {
                printf("%d ",i*BYTESIZE + j);
            }
        }
        pBuffer++;
    }
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    BitMapSortDemo();
    return 0;
}
1.2 快速去重
  • 问题:
    2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
  • 分析:
      首先,根据“内存空间不足以容纳这2.5亿个整数”我们可以快速的联想到Bit-map。下边关键的问题就是怎么设计我们的Bit-map来表示这2.5亿个数字的状态了。其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2 bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间几十兆左右。
      接下来的任务就是遍历一次这2.5亿个数字,如果对应的状态位为00,则将其变为01;如果对应的状态位为01,则将其变为11;如果为11,,对应的转态位保持不变。
      最后,我们将状态位为01的进行统计,就得到了不重复的数字个数,时间复杂度为O(n)。
1.3 快速查询

        同样,我们利用Bit-map也可以进行快速查询,这种情况下对于一个数字只需要一个bit位就可以了,0表示不存在,1表示存在。假设上述的题目改为,如何快速判断一个数字是够存在于上述的2.5亿个数字集合中。
  同之前一样,首先我们先对所有的数字进行一次遍历,然后将相应的转态位改为1。遍历完以后就是查询,由于我们的Bit-map采取的是连续存储(整型数组形式,一个数组元素对应32bits),我们实际上是采用了一种分桶的思想。一个数组元素可以存储32个状态位,那将待查询的数字除以32,定位到对应的数组元素(桶),然后再求余(%32),就可以定位到相应的状态位。如果为1,则代表改数字存在;否则,该数字不存在。



2. 扩展
  • Bloom Filter(布隆过滤器)
    当一个元素被加入集合中时,通过k各散列函数将这个元素映射成一个位数组中的k个点,并将这k个点全部置为1.
    有一定的误判率--在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误判为属于这个集合.因此,它不适合那些"零误判"的应用场合.在能容忍低误判的应用场景下,布隆过滤器通过极少的误判换区了存储空间的极大节省.

    Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为1(1≤i≤k)。注:如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。

    在判断y是否属于这个集合时,对y应用k次哈希函数,若所有hi(y)的位置都是1(1≤i≤k),就认为y是集合中的元素,否则就认为y不是集合中的元素。

    Bloom filter可以看做是对bit-map的扩展(关于Bloom filter参见:海量数据处理之Bloom filter详解)。

3. 实战
  • 适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下
  • 基本原理及要点:使用bit数组来表示某些元素是否存在,比如8位电话号码
  • 扩展:bloom filter可以看做是对bit-map的扩展

(1) 已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。

  • 8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。 (可以理解为从0-99 999 999的数字,每个数字对应一个Bit位,所以只需要99M个Bit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了所有的8位数的电话)

(2) 2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。

  • 将bit-map扩展一下, 采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存232*2bit=1GB内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。


总结

使用Bit-map的思想,我们可以将存储空间进行压缩,而且可以对数字进行快速排序、去重和查询的操作。Bloom Fliter是Bit-map思想的一种扩展,它可以在允许低错误率的场景下,大大地进行空间压缩,是一种拿错误率换取空间的数据结构。

引用

参考 1
参考 2
参考 3
参考 4: july
参考 5
参考 6

你可能感兴趣的:(位图-BitMap)