当实现时间复杂度优化的时候,经常会用到hash表来存储一些数据,然后查找,来提高时间复杂度。如果直接设置int arr[]数组,对于大数据来说,会需要很大的空间来存储,严重影响效率。而bitmap是用位来存储数据的:如果只是表示有无,每个数据分配1位;如果表示多个,可能需要给每个数据分配多位(例如统计大数据中不重复的个数,用0、1、2来表示状态)
bitmap在对数据进行排序时,其复杂度为O(n)。当然这是拿空间换来的。与bitmap类似的还有Bloom filter,Bloom filter可以看做是对bit-map的扩展。
样例1:对数组array[4,7,2,5,3]排序
bitmap采用的是以空间换时间的思想,数组中最大元素值为7,所以在内存中开辟8位的存储空间,存储空间大小的确定方法是(元素最大值/8+1),之所以除以8,是因为开辟空间的时候以byte为单位,1byte=8bit。
开辟8位的空间后,每位初始化为0,如下表:
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
开始遍历array数组,array[0]=4时,则将4号位’置1,变为下表:
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
array[1]=7时,则将7号位置1,变为下表:
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
直至遍历完array数组,空间各位如下表:
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 |
从头开始遍历空间中各位,不为0的输出其为号,得:2,3,4,5,7,其效率为O(n)=8
样例2:array[1,2,5,8,10]为例,涉及到两个字节
初始化空间如下表:
0号位 | 1号位 | 2号位 | 7号位 | 8号位 | 15号位 | 15号位 | |||||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
申请时以字节申请空间:g_bitmap = (char *)malloc((size/8+1)*sizeof(char));
此时将上表从7号位处分开(即2个字节的空间,定义字节数组byte[]):
0号位 | 1号位 | ... | 7号位 |
0 | 0 | ... | 0 |
0号位 | 1号位 | ... | 7号位 |
0 | 0 | ... | 0 |
读取array[0]=1时,
array[0]/8=0——>byte[0]
array[0]%8=1——>1号位,即将byte[0]中的1号位置1,
读取array[4]=10时,
array[0]/8=1——>byte[1]
array[0]%8=2——>2号位,即将byte[1]中的2号位置1,
最后,空间变为:
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
0号位 | 1号位 | 2号位 | 3号位 | 4号位 | 5号位 | 6号位 | 7号位 |
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
遍历每个byte[]下的每位,为1的输出(逆运算得到该位在array[]中的值),
实现代码:
bitmap.h
- /*
- *bitmap的c语言实现
- *作者:bitileaf
- *时间:2010-12-18 14:12
- */
- #ifndef _BITMAP_H_
- #define _BITMAP_H_
- /*
- *功能:初始化bitmap
- *参数:
- *size:bitmap的大小,即bit位的个数
- *start:起始值
- *返回值:0表示失败,1表示成功
- */
- int bitmap_init(int size, int start);
- /*
- *功能:将值index的对应位设为1
- *index:要设的值
- *返回值:0表示失败,1表示成功
- */
- int bitmap_set(int index);
- /*
- *功能:取bitmap第i位的值
- *i:待取位
- *返回值:-1表示失败,否则返回对应位的值
- */
- int bitmap_get(int i);
- /*
- *功能:返回index位对应的值
- */
- int bitmap_data(int index);
- /*释放内存*/
- int bitmap_free();
- #endif
bitmap.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "bitmap.h"
- unsigned char *g_bitmap = NULL;
- int g_size = 0;
- int g_base = 0;
- int bitmap_init(int size, int start)
- {
- g_bitmap = (char *)malloc((size/8+1)*sizeof(char));
- if(g_bitmap == NULL)
- return 0;
- g_base = start;
- g_size = size/8+1;
- memset(g_bitmap, 0x0, g_size);
- return 1;
- }
- int bitmap_set(int index)
- {
- int quo = (index-g_base)/8 ;
- int remainder = (index-g_base)%8;
- unsigned char x = (0x1<<remainder);
- if( quo >= g_size)
- return 0;
- g_bitmap[quo] |= x;
- return 1;
- }
- int bitmap_get(int i)
- {
- int quo = (i)/8 ;
- int remainder = (i)%8;
- unsigned char x = (0x1<<remainder);
- unsigned char res;
- if( quo >= g_size)
- return -1;
- res = g_bitmap[quo] & x;
- return res > 0 ? 1 : 0;
- }
- int bitmap_data(int index)
- {
- return (index + g_base);
- }
- int bitmap_free()
- {
- free(g_bitmap);
- }
测试程序:bitmap_test.c
- #include <stdio.h>
- #include "bitmap.h"
- int main()
- {
- int a[] = {5,8,7,6,3,1,10,78,56,34,23,12,43,54,65,76,87,98,89,100};
- int i;
- bitmap_init(100, 0);
- for(i=0; i<20; i++)
- bitmap_set(a[i]);
- for(i=0; i<100; i++)
- {
- if(bitmap_get(i) > 0 )
- printf("%d ", bitmap_data(i));
- }
- printf("\n");
- bitmap_free();
- return 0;
- }
评注:
bitmap_init()函数:
g_size = size/8+1表示需要多少字节;
g_base = start;表示从几开始(例如数据都是在100到200范围内,那么start为100)
调用memset函数初始化为0.
bitmap_set()函数:
quo = (index-g_base)/8 来求存储在哪个字节当中;
remainder = (index-g_base)%8;表示在字节中的哪一位
x = (0x1<<remainder);为remainder位置1
g_bitmap[quo] |= x;设置位
bitmap_get()函数:这个函数传递的参数是0--> size-start,实际值通过bitmap_data()函数转化
res = g_bitmap[quo] & x;求出对应位的值
【问题实例】
1)已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。 (可以理解为从0-99 999 999的数字,每个数字对应一个Bit位,所以只需要99M个Bit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了所有的8位数的 电话)
2)海量日志数据,提取出某日访问百度次数最多的那个IP。
此题,在我之前的一篇文章:“从头到尾彻底解析Hash表”算法里头有所提到,当时给出的方案是:IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将ip直接存入内存,然后进行统计。
再详细介绍下此方案:首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
3)2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
方案1:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
方案2:也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。