数据结构------位图的基本操作

        在之前的哈希表中,如果要在表中存放一个整数,此时就要申请一个整型的内存来存放它,一个整型数据在32位或64位平台下都占4个字节。如果现在需要存储的数据非常多,比如说40亿个不重复的数据,就需要160亿个字节来存储,1GB的内存表示的是10亿个字节,此时就需要16GB的内存来存放这些数据,而我们普通的电脑内存一般都是4G的内存,这显然是存放不下的。我们知道,内存中的最小单位是比特位。如果能用一个比特位来存放一个整型,只需要0.5GB的内存。

        一个比特位可以表示一个0或1。如果要表示40亿个数据,可以申请0.5GB的内存。如果要存放的数据为10,就将第10个比特位设置为1。如果要查找的数据为100,就查看第100个比特位处的状态,如果为1说明,100存在于这堆数据中,如果为0说明不存在。

位图的概念

        像上述所描述的,在一个结构中,用一个比特位来描述一个数据的状态,这种结构就称为位图。位图实际上是哈希表的一种变形。

1. 位图的结构定义

        首先,该位图中最大能表示的比特位个数需要提前设定。

        然后,根据最大比特位数来进行内存的申请。内存不能以比特位为单位进行申请,所以可以自己选用一种数据类型来申请内存,这里以64位8字节为一个数组元素长度进行内存的申请。

        结构定义如下:

#include<stdint.h>//uint64_t需引用该头文件

//位图以uint64_t为单位进行内存的申请                                                                            
typedef uint64_t BitMapType;

//指定位图最大能表示的数字范围
#define MAXSIZE 1000

//位图的结构定义
typedef struct BitMap
{
    BitMapType* data;
    uint64_t capacity;//位图中最大能表示的比特位个数
}BitMap;

2. 位图的初始化

        初始化时要指定位图所能存储的最大比特位个数。然后根据该个数进行内存的申请。如果最大能表示的比特位为100,因为我们是以64位8字节(数组元素类型)为单位进行申请,此时需要2个数组元素的内存大小;如果最大比特位为300,此时需要5个数组元素的内存大小,所以,最大比特位capacity与64位的数组元素个数n之间满足:n = capacity/64 + 1;

        在申请完内存之后,内存中存放的都是随机值,为方便后续的操作,将内存中的状态均置为0.

        代码如下:

//位图的初始化
void BitMapInit(BitMap* bm,uint64_t capacity)
{
    if(bm == NULL)
    {                                                                                                           
        //非法输入
        return;
    }

    bm->capacity = capacity;
    //如果位图最大表示100个数字,则需要2个64位的内存
    //如果位图最大表示200个数字,则需要4个64位的内存
    //如果位图最大表示300个数字,则需要5个64位的内存
    //malloc以字节为单位进行内存申请

    //需要申请多少的64位内存
    uint64_t size = bm->capacity/(sizeof(BitMapType)*8) + 1;

    bm->data = (BitMapType*)malloc(size * sizeof(BitMapType));
    //初始化时要将位图中的各个位均置为0,以便后续的操作
    memset(bm->data,0,size*sizeof(BitMapType));
    return;
}

3. 判断位图中的某一位是否为1

        思路如下:

(1)首先,如果该位的大小超过了位图最大能表示的范围,则判断失败

(2)如果不为(1)。则计算该位在位图结构中的哪个数组元素上,以及该数组元素的哪个比特位上。

        如果要判断的位是100,一个数组元素的大小为64位,则n = 100/64 = 1,表示该位在数组下标为1的元素上;

        一个数组元素有64位,100具体在哪个位上,还需要计算:offset = 100%64 = 36,表示100在下标为1的数组元素的第36位(从0开始计数)上。

(3)知道了指定位所在的位置,此时就要判断该位是否为1了。可以通过位运算来判断。因为指定位在下标为n的数组元素上的偏移量为offset,所以,先将1左移offset位之后,再与下标为n的数组元素进行按位与操作,如果指定位为1,则按位操作后的结果必不为0,如果按位操作后的结果为0,则按位操作后的结果必为0。此时,只需将按位操作后的结果与0进行比较即可。

        根据上述思路,代码实现如下:

//测试某一位是否为1
int BitMapTest(BitMap* bm,uint64_t index)                                                                       
{
    if(bm == NULL || index >= bm->capacity)
    {
        //非法输入
        return 0;
    }
    uint64_t n;
    uint64_t offset;
    //获取index所在的数组元素下标及偏移量                                                                       
    GetOffset(index,&n,&offset);
   
    //用1与之按位与,如果结果为0,则该位为0,否则为1
    //如果该位为1时,按位与完的结果必定只有该位为1,其余位为0,所以对结果进行判断时
    //只能跟0进行比较,不能跟1进行比较
    uint64_t ret = bm->data[n] & (0x1ul << offset);
    if(ret == 0)
    {
        return 0;
    }
    else
    {
        return 1;
    }                                                                                                           
}

        在上述代码中的语句:

uint64_t ret = bm->data[n] & (0x1ul << offset);

        需要注意两点:

(1)在对0x1进行左移时,因为1是一个整型数据,最多有32位,如果offset的值大于32,就会发生错误,所以要将1强转为无符号长整型(在64位平台下为8字节)

(2)如果需判断的比特位的偏移量大于32,则按位与完后的结果就不能用一个整型数据来进行存放,所以结果ret要用uint64_t类型的数据来进行接收。

        上述代码中的获取偏移量函数为:

//将获取指定位所在的数组下标及偏移量封装为函数                                                                  
void GetOffset(uint64_t index,uint64_t* n,uint64_t* offset)
{
    //首先计算该位在哪个数组元素内(数组元素以64为为一个单元)
    //计算完之后的n表示该位在下标为n的数组元素内(数组下标从0开始计数)
    *n = index/(sizeof(BitMapType)*8);

    //在计算该位在该元素的哪个位上
    //计算完之后的offset表示index在下标为的数组元素的哪一位(从0开始计算)
    *offset = index % (sizeof(BitMapType)*8); 
    return;
}

4. 给某一位设置为1

(1)首先要判断该位有没有超过位图所能表示的最大范围,如果超过,则设置失败

(2)没有超过,则计算该位的所在的数组元素下标及偏移量。

(3)通过按位运算将该数组下标的偏移量处的比特位设置为1。

        在对该位设置为1时,同时也要保证其他位不变,所以该位可以与1进行按位或运算,其他位与0进行按位或运算,即可达到目的。

        根据上述思路,实现代码如下:

//给某一位设置为1                                                                                               
void BitMapSet(BitMap* bm,uint64_t index)
{
    if(bm == NULL || index >= bm->capacity)
    {
        //非法输入
        return;
    }

    //首先计算该位所在的数组下标及偏移量
    uint64_t n;
    uint64_t offset;
    GetOffset(index,&n,&offset);

    //再将该位设置为1
    //将该位置为1时要保证其他其他位不变,此时就要使其他位与0进行按位或
    //因此,该位要与1进行按位或,才能保证将该位置为1,而其他位也能保持不变

    //按位操作后的结果不会改变原来的值,所以要对其进行赋值
    bm->data[n] = bm->data[n] | (0x1ul << offset);
    return;

}

5. 给某一位设置为0

        思路与上述相同:

(1)首先要判断该位有没有超过位图所能表示的最大范围,如果超过,则设置失败

(2)没有超过,则计算该位的所在的数组元素下标及偏移量。

(3)通过按位运算将该数组下标的偏移量处的比特位设置为0。

        要将指定位设置为0,则需要将该位与0进行按位与操作,同时保证其他位不变,则其他位与1进行按位与操作。

        代码实现如下:

//给某一位设置为0
void BitMapSet0(BitMap* bm,uint64_t index)
{
    if(bm == NULL || index >= bm->capacity)
    {
        //非法输入
        return;
    }
    //首先计算index所在的数组元素下标以及在在该下标处的偏移量
    uint64_t n;
    uint64_t offset;
    GetOffset(index,&n,&offset);

    //在根据数组元素下标和偏移量将该位设置为0
    //在将该位设置为0时,要保证其他位保持不变
    //所以,其他位要与1进行按位与(其他位也可以与0进行按位或但是index所在的为要置为0,只能进行按位与运算)
    //该位要与0进行按位与
    bm->data[n] = bm->data[n] & ~(0x1ul << offset);
}

6. 将位图的所有位均置为1

(1)首先要计算该位图共占用了多大字节的内存

(2)利用memset函数将内存中的每个比特位均设置为1,因为该函数是以字节为单位进行设置的,所以要使一个字节的8个比特位均为1,则需要将一个字节设置为0xff。

        代码实现如下:

//将位图的所有位均置为1
void BitMapFill(BitMap* bm)
{
    if(bm == NULL)
    {
        //非法输入
        return;
    }
    //将位图所占的内存区域中的所有位均置为1
    //使用memset来进行置1,位图有多少字节,就置多少个0xff                                                       

    //首先计算位图中共有多少64为的内存单元
    uint64_t size = bm->capacity/(sizeof(BitMapType)*8) + 1;
    memset(bm->data,0xff,size*sizeof(BitMapType));
    return;
}

7. 将位图的所有位均置为0

        与上述思路相同,只需将0xff改为0即可。

//将位图的所有位均置为0
void BitMapClear(BitMap* bm)
{
    if(bm == NULL)
    {
        //非法输入
        return;
    }
    //将位图中的所有比特位均置为0
    //首先,计算位图中共占用了多少个64位的内存单元
    uint64_t size = bm->capacity/(sizeof(BitMapType)*8) + 1;
    //然后按字节将所有的比特位均置为0
    memset(bm->data,0x0,size*sizeof(BitMapType));
    return;                                                                                                     
}

8. 销毁位图

        因为位图中的内存是动态申请而得,所以,销毁位图时要将其进行释放,同时要将位图所能表示的最大比特位数设置为0.

//销毁位图
void BitMapDestroy(BitMap* bm)
{
    if(bm == NULL)
    {
        //非法输入
        return;
    }

    bm->capacity = 0;
    free(bm->data);
    return;
}












猜你喜欢

转载自blog.csdn.net/sandmm112/article/details/80420141
今日推荐