位图算法(编程珠玑例题)

输入:一个最多包含n个正整数的文件,最大不超过一千万,每个数最多只出现一次,数据之间没有明显的关联

输出:升序排列的整数列表

要求:只有大约1MB的内存可用,磁盘空间充足,运行时间最多不超过1分钟,10秒左右就可以不需要优化

 

分析:1MB的内存只能存储大约250000个int型整数,远远低于1千万的要求,但是每个int型整数有32位,如果用第i位的0,1来表示数据i的存在与否,一千万的整数需要1000 0000/32=312500个整数,他们将占有312500*4=1250000个比特,即大约1.2MB的内存。

 

但如果要求内存上限是1MB的话,可以使用两趟来实现,一次一半,使用5000000/8=625000个字节的空间进行从0~4999999的排序,(通过控制仅仅读入范围在0到4999999的数字,其他的忽略掉)之后把结果存储在磁盘中,然后再排序5000000~9999999的整数,把结果跟刚才的结果组合起来即可。

 

#include #define BITS 32; #define SHIFT 5; #define MASK 31; #define N 10000000; int a[1+N/BITS]; void clr(int i){ a[i>>SHIFT]&=~(1<<(i&MASK)); } void set(int i){ a[i>>SHIFT]|=(1<<(i&MASK)); } void test(int i){ a[i>>SHIFT]&=(1<<(i&MASK)); } int main() { int i; for(i=0;i

 

其中SHIFT=log(32)=5, i>>SHIFT=i/32(25=32),在整个a数组中,每个元素都可以代表32个整数的存在,所以在一千万个数中我们应该先确定数字i在那个数组中,很明显他应该位于i/32的位置,即a[i>>SHIFT]

 

当我们想对一个int整数的第x位置1的操作的时候:

int bitrow=0;

bitrow |=1<<(x);

以上代码可以做到,在我们的i中,i=32*n+x,这个x将决定i值在对应的数组元素中哪个位置,所以对i进行模运算可以解决,则set(int i)实现方式为:

a[i/32] |= 1<<(i%32)   等价于 a[i>>SHIFT] |=1<<(i&MASK)

 

总结:

a%BITSPERROW==a&(BITSPERROW-1)==a&MASK

a/BITSPERROW==a>>log(BITSPERROW)==a&SHIFT 

 

另外一个类似的问题:

 

Question:给40亿个不重复的无序整数集合,要求判断一个数是否属于这个集合?

Ans:以上解法给出O(1)复杂度的解决方案,但是需要注意40亿个数占用约480MB的内存,可以考虑分次使用多趟算法来实现,方法上面已经叙述过了,K趟算法可以在Kn的时间开销和n/k的空间开销内完成最多n个小于n的无重复整数排序。

 

 

位图排序其实是一种HASH映射,把对应的正整数映射到位图集合中,每一个bit代表一个整数的存在与否,这种方法可以应用在海量规模数据的快速查找,时间复杂度O(1),但空间复杂度稍高,需要注意的这种方式要求数据最好不要有重复,否则实现起来会比较麻烦,

下面给出数据有重复的思路,假设数据至多重复10次:

 

对于无重复的case来说,一个数据位就可以表示有无,可以理解为重复度为1,但是如果重复度超过1,则一个数据位就无法表达,根据2进制数据位来说,若想表达10的重复度,至少需要4个数据位,也就是说,把前一个case做了适当的推广,由一个bit推广到4个bits就可以解决问题。

 

 

#include #define N 10000000 #define SHIFT 3 #define MASK 7 #define BITS 32 int a[1+N/BITS]; void set_add(int i) { int label=i<>pos)&0x0f; temp++; a[label]&=~(0x0f<>SHIFT]&=~(0x0f<<(i&MASK)); } int test(int i) { return (a[i>>SHIFT]>>(4*(i&MASK)))&0x0f; } int main() { int i; for(i=0;i0) printf("%d",i); return 0; }  

set_add的操作其实不难理解,由于要计算重复度,所以由上面的“置位”操作改为“加一”操作,我们需要用4个bit位来表示一个整数出现的次数。我们把4个bit当成“一个bit”来看,这样的话还是“一个bit”表示一个整数,那么一个int型数组元素就有8个这样的“比特”,延续上面的思路,我们首先要定位它会出现在哪个int数组元素中:

int label=i<<3 , 3=log8

之后再确定它会对应该元素中的哪一个“bit”(一共有8个):

int pos=4*(i&MASK),乘以4是因为对于每一个i的增长,我们希望对应在a[]中已定位到的元素中4个比特位的跳跃——4个比特位被我们看成“一个比特”,以防止数据存储相互冲突

之后取出这个“bit”,每次加一,然后再重新写入原来的对应位中。

关于重复度的思路来自:http://blog.csdn.net/clearriver/archive/2009/07/19/4361941.aspx

 

你可能感兴趣的:(算法学习)