先略提下位图:位图是建立在哈希表的基础上进行的优化,哈希表可能用1个字节或者4个字节来表示key,如果数据太多,哈希表就会占用大量空间。为了解决这个问题,聪明的人类发明了了位图。找到key的用一个bit来存储一个数的状态,0表示不存在,1表示存在。这样查找key是只需判断对应bit是1还是0就行,解决了哈希表占用空间的问题。
例题:位图应用
给定100亿个整数,设计算法找到只出现一次的整数
使用位图,就是用1个bit位表示一个状态(或者表示次数),可以缓解大量数据相关问题。
100亿个整数,如果是int型的话,在32位机器下int型表示范围为-2^31~2^31-1,共2^32个数字。而100亿个数字的话,远远超出int表示范围,这就意味着有大量数据重复了,而且有些数据可能出现不止一次。但既然这个题目让我们找到只出现一次的整数,那么我们用2个bit来表示一个整数出现次数就完全O什么K了。从左向右存储表示,00表示出现0次,10表示出现1次,11表示出现不止一次。
2个bit表示一个数字,如果要存储2^32个数字的状态就需要2 ^ 32 / 8 * 2字节的空间。一个字节就可以存储4个整数的状态了。我们用数组的方式存取数据。key表示查找表示所要查找的整数,因为2个bit表示一个数字,所以key/16才是存储key的元素下标,key%16和key%16+1就是key的两个状态位。用1左移对应长度,然后比较,就可判断key是否出现一次。
代码:
BitMap.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef struct BitMap
{
size_t* _bits;
size_t _range;
}BitMap;
void BitMapInit(BitMap* bm, size_t range);
void BitMapSet(BitMap* bm, size_t x);
void BitMapReset(BitMap* bm, size_t x);
// x存在返回0,不存在返回-1
int BitMapTest(BitMap* bm, size_t x);
void BitMapOnceTime(BitMap*bm, size_t x);//数字出现一次
BitMpa.c
#include"BitMap.h"
void BitMapInit(BitMap* bm, size_t range)//初始化
{
bm->_bits = (size_t*)malloc(range / 4 + 1);
assert(bm);
memset(bm->_bits, 0, range / 4 + 1);
}
void BitMapSet(BitMap* bm, size_t x)//置1,两个bit表示状态,00、10、11表示0次,1次,2次及以上
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
if (BitMapTest(bm,x) == -1)//出现0次
{
bm->_bits[_index] |= (1 << _position);
}
else
{
if (((bm->_bits[_index]) & (1 << (_position + 1))) == 0)//出现1次
bm->_bits[_index] |= (1 << (_position + 1));
else//出两次及以上,不变
return;
}
}
void BitMapReset(BitMap* bm, size_t x)//当前位置清零
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
bm->_bits[_index] &= ~(1 << _position);
bm->_bits[_index] &= ~(1 << (_position + 1));
}
// x存在返回0,不存在返回-1
int BitMapTest(BitMap* bm, size_t x)
{
int _index = x / 16;
int _position = x % 16 * 2;
if ((((bm->_bits[_index]) & (1 << _position)) != 0)
|| (((bm->_bits[_index]) & (1 << (_position + 1))) != 0))
return 0;
else
return -1;
}
void BitMapOnceTime(BitMap*bm, size_t x)//数字出现一次
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
if ((((bm->_bits[_index]) & (1 << _position)) != 0)
&& (((bm->_bits[_index]) & (1 << (_position + 1))) == 0))
printf("%u appear once.\n",x);
else
printf("%u isn't once.\n",x);
}
Test.c
#include"BitMap.h"
void TestAppearOnce()
{
BitMap bt;
BitMapInit(&bt, -1);//range是size_t型,-1存进去就是全1,表示整型最大数
BitMapSet(&bt, 99);//只能插入非负数
BitMapSet(&bt, 99);
BitMapSet(&bt, 16);
BitMapSet(&bt, 16);
BitMapSet(&bt, 16);
BitMapSet(&bt, 88);
BitMapSet(&bt, 1024);
BitMapSet(&bt, 4200000000);
BitMapOnceTime(&bt, 99);
BitMapOnceTime(&bt, 16);
BitMapOnceTime(&bt, 88);
BitMapOnceTime(&bt, 1024);
BitMapOnceTime(&bt, 4200000000);
}
int main()
{
TestAppearOnce();
return 0;
}
1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
100亿个int类型的数据,则数据范围在-2^31~2^21-1,2个bit表示一个数据出现的次数,只需2^32 * 2个bit就可。而2^32*2bit就是1G,因此内存使用满足题目条件。用从左向右存储,00、10、01、11分别表示0次,1次,2次,3次。类似100亿个整数查找只出现一次的整数,即可找到满足题目要求的整数。
代码
BitMapAppearNoMoreTwice.h
//1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef struct BitMap
{
size_t* _bits;
size_t _range;
}BitMap;
void BitMapInit(BitMap* bm, size_t range);
void BitMapSet(BitMap* bm, size_t x);
void BitMapReset(BitMap* bm, size_t x);
void BitMapAppearNoMoreTwiceTime(BitMap*bm, size_t x);//数字出现一次
void Destory(BitMap* bt);//销毁
BitMapAppearNoMoreTwice.c
#include"BitMapAppearNoMoreTwice.h"
void BitMapInit(BitMap* bm, size_t range)//初始化
{
bm->_bits = (size_t*)malloc(range / 4 + 1);
assert(bm);
memset(bm->_bits, 0, range / 4 + 1);
}
//2个bit位表示一块,标识一个数。0-2147483647块标识正数,2147483647-4294967295块标识负数
void BitMapSet(BitMap* bm, size_t x)//置1,两个bit表示状态,00、01、10、11表示0次,1次,2次、3次及以上
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
if ((((bm->_bits[_index]) & (1 << _position)) == 0)
&& (((bm->_bits[_index]) & (1 << (_position + 1))) == 0))//出现0次
{
bm->_bits[_index] |= (1 << _position);
}
else if ((((bm->_bits[_index]) & (1 << _position)) != 0)
&& (((bm->_bits[_index]) & (1 << (_position + 1))) == 0))//出现1次
{
bm->_bits[_index] |= (1 << (_position + 1));//高位置1
bm->_bits[_index] &= ~(1 << _position);//低位置0
}
else if ((((bm->_bits[_index]) & (1 << _position)) == 0)
&& (((bm->_bits[_index]) & (1 << (_position + 1))) != 0))//出现两次
{
bm->_bits[_index] |= (1 << _position);//低位置1
}
else
return;
}
void BitMapReset(BitMap* bm, size_t x)//当前位置清零
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
bm->_bits[_index] &= ~(1 << _position);
bm->_bits[_index] &= ~(1 << (_position + 1));
}
void BitMapAppearNoMoreTwiceTime(BitMap*bm, size_t x)//数字出现不超过两次
{
assert(bm);
int _index = x / 16;
int _position = x % 16 * 2;
if ((((bm->_bits[_index]) & (1 << _position)) != 0)
&& (((bm->_bits[_index]) & (1 << (_position + 1))) != 0))
{
printf("%d appear more than twice.\n", x);
}
else
{
printf("%d appear no more twice.\n", x );
}
}
void Destory(BitMap* bt)//销毁
{
assert(bt);
free(bt->_bits);
bt->_bits = NULL;
bt->_range = 0;
}
Test.c
#include"BitMapAppearNoMoreTwice.h"
void TestAppearOnce()//只能插入-2147483648到2147483647之间的数,因为这是int型范围
{
BitMap bt;
BitMapInit(&bt, -1);//range是size_t型,-1存进去就是全1,表示整型最大数
BitMapSet(&bt, 99);
BitMapSet(&bt, 99);
BitMapSet(&bt, -16);
BitMapSet(&bt, -16);
BitMapSet(&bt, 88);
BitMapSet(&bt, 88);
BitMapSet(&bt, 88);
BitMapSet(&bt, 1024);
BitMapSet(&bt, 1024);
BitMapSet(&bt, 1024);
BitMapSet(&bt, 1024);
BitMapSet(&bt, -2100000000);
BitMapSet(&bt, -2100000000);
BitMapAppearNoMoreTwiceTime(&bt, 99);
BitMapAppearNoMoreTwiceTime(&bt, -16);
BitMapAppearNoMoreTwiceTime(&bt, 88);
BitMapAppearNoMoreTwiceTime(&bt, 1024);
BitMapAppearNoMoreTwiceTime(&bt, -2100000000);
Destory(&bt);
}
int main()
{
TestAppearOnce();
return 0;
}
给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集
两个文件,一个bit位就可标识整数key是否存在。2^32bit就可以表示一个文件,2个文件共需1G,满足题意。然后判断对应位置状态位是否都为1即可。
代码
BitMap.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef struct BitMap
{
size_t* _bits;
size_t _range;
}BitMap;
void BitMapInit(BitMap* bm, size_t range);
void BitMapSet(BitMap* bm, size_t x);
void BitMapReset(BitMap* bm, size_t x);
int BitMapTest(BitMap* bm, size_t x);// x存在返回0,不存在返回-1
void JudgeTwoBitMapSame(BitMap *bt1, BitMap *bt2);//判断两个位图相同地方
void Destory(BitMap* bt);//销毁
BitMap.c
#include"BitMap.h"
void BitMapInit(BitMap* bm, size_t range)
{
bm->_bits = (size_t*)malloc(range / 8 + 1);
bm->_range = range;
assert(bm);
memset(bm->_bits, 0, bm->_range / 8 + 1);
}
void BitMapSet(BitMap* bm, size_t x)
{
assert(bm);
int _index = x / 32;
int _position = x % 32;
bm->_bits[_index] |= (1 << _position);
}
void BitMapReset(BitMap* bm, size_t x)
{
assert(bm);
int _index = x / 32;
int _position = x % 32;
bm->_bits[_index] &= ~(1 << _position);
}
// x存在返回0,不存在返回-1
int BitMapTest(BitMap* bm, size_t x)
{
int _index = x / 32;
int _position = x % 32;
if (((bm->_bits[_index]) & (1 << (_position))) != 0)
return 0;
else
return -1;
}
void JudgeTwoBitMapSame(BitMap *bt1, BitMap *bt2)//判断两个位图相同地方
{
assert(bt1);
assert(bt2);
size_t i = 0;
int _index;
int _position;
for (i = 0; i < bt1->_range; i++)//此处i<bit1的范围,而bt1->_range=2^32,循环会进行较长时间\
且不能判断2^32-1位置的数,若i=2^32-1则会死循环
{
_index = i / 32;
_position = i % 32;
if (((bt1->_bits[_index] & (1<<_position)) != 0)
&& ((bt2->_bits[_index] & (1 << _position)) != 0))
printf("%u ", i);
}
}
void Destory(BitMap* bt)//销毁
{
assert(bt);
free(bt->_bits);
bt->_bits = NULL;
bt->_range = 0;
}
Test.c
#include"BitMap.h"
void TestTwoBitMapSame()
{
BitMap bt1;
BitMap bt2;
BitMapInit(&bt1, -1);//range是size_t型,-1存进去就是全1,表示整型最大数
BitMapInit(&bt2, -1);
BitMapSet(&bt1, 99);//只能插入非负数
BitMapSet(&bt1, 666);
BitMapSet(&bt1, 16);
BitMapSet(&bt1, 2333);
BitMapSet(&bt1, 11);
BitMapSet(&bt1, 88);
BitMapSet(&bt1, 1996);
BitMapSet(&bt1, 1024);
BitMapSet(&bt1, 1024);
BitMapSet(&bt1, 4294967294);//4294967294该数是最大存储的数字,2^32-1-1,\
因为位图从0开始存储,空间是2^32-1,
BitMapSet(&bt2, 100);
BitMapSet(&bt2, 6954);
BitMapSet(&bt2, 759);
BitMapSet(&bt2, 2048);
BitMapSet(&bt2, 2333);
BitMapSet(&bt2, 16);
BitMapSet(&bt2, 1024);
BitMapSet(&bt2, 1996);
BitMapSet(&bt2, 4294967294);
JudgeTwoBitMapSame(&bt1, &bt2);
printf("\n");
BitMapReset(&bt2, 1996);
JudgeTwoBitMapSame(&bt1, &bt2);
printf("\n");
Destory(&bt1);
Destory(&bt2);
}
int main()
{
TestTwoBitMapSame();
return 0;
}