《编程珠玑》学习总结1—bitmap

    今天刚买到《编程珠玑》于是开始翻,第一章引出的磁盘排序问题。

    问题描述:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7,且所有正整数都不重复。求如何将这n个正整数升序排列。

    条件:最多有1MB的内存空间可用,有充足的磁盘存储空间。

     方法一:多通道法

     思想描述:内存1MB可以储存的int(4byte)有10^3*10^3/4=250 000个号码。而包含正整数的文件约为10^7个int大小。这意味着无法将所有文件中的正整数一次读取进入到内存空间中去进行排序算法,所以一次一次读出来进行排序。

    多通道方法:

    第1遍遍历文件,将文件中范围在1~ 249 999的正整数读取进入1MB内存,排序(可以使用各种排序方法),将排序后的正整数存储在磁盘文件temp中 
    第2遍遍历文件,将文件中范围在250 000~499 999的正整数读取进入1MB内存,排序,将排序后的正整数加入存储在磁盘文件temp中 
   …. 
    第40遍遍历文件,将文件中范围在10^7-250 000~10^7的正整数读取进入1MB内存,排序,将排序后的整数加入存储在磁盘文件temp中 
    输出temp文件 。
 
    此方法缺点是非常明显的:需要遍历40次文件,意味着读取输入文件40次,并且需要一个和中间文件temp。
    方法二:bit-map方法

    我们想使用hash映射,将对应的正整数映射到位图集合中。即将正整数映射到bit集合中,每一个bit代表其映射的正整数是否存在。比如{1,2,3,5,8,13}使用下列集合表示:

    0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
    我们可以使用具有10^7位的字符串来表示这个文件。其中,当且仅当整数i在文件中存在时候,第i位为1
    bitmap需要的存储空间大小为10^7/8=1.25M,基本可以满足程序需求,

解决此问题的步骤为:

   创建有个10^7位(10^7/8/1024/1024≈1MB)的字符串,并将其每一bit设置为0;   
   读取包含正整数的文件,对每一个i,将内存中bit[i] 位设置成1.   
   按位顺序读取字符串。当读取到bit[j] 为1时输出(int)j。 

伪代码如下:

for i= [0,n]
    bit[i]=0;
for each i in the input file
     bit[i]=1;
for i=[0,n]
    if bit[i] == 1 
      write i  on the output file
     位图的核心在于用位存储(因为 数据无重复,1bit已经足够表示)加上位映射int原数,立马解决了内存不足的问题,其实还可以用来解决一些海量数据问题。

位图法的C语言实现:

/* Copyright (C) 1999 Lucent Technologies */
/* From 'Programming Pearls' by Jon Bentley */

/* bitsort.c -- bitmap sort from Column 1
 *   Sort distinct integers in the range [0..N-1]
 */

#include <stdio.h>

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

int main()
{	int i;
	for (i = 0; i < N; i++)
		clr(i);

	while (scanf("%d", &i) != EOF)
		set(i);
	for (i = 0; i < N; i++)
		if (test(i))
			printf("%d\n", i);
	return 0;
}
STL中的bistset封装了一些位的写法,上面代码用C++的bitset修改如下:

#include <iostream>
#include <bitset>
using namespace std;

const int N =  10000000;

int main()
{
	bitset<N> bit;
	int i;
	
	while (cin >> i)
		bit.set(i);
	for (i = 0; i < N; i++)
		if (bit.test(i))
			cout << i << endl;
		
		return 0;
}

     书后提的问题也蛮好

1、如何生成0至n-1之间的k个不同的随即顺序的随机整数,程序尽量简短有效。

      个人解答:如果稍微偷懒一点,采用java或者c+stl中的set类型,生成随机数,插入即可;如果是c语言实现,构建红黑树,插入时比较,相同则舍弃(后来发现这方法是错的)

2、我们的程序需要1.25M,如果严格限制1M呢?

       貌似除了跑两次还真想不出其他法子了

3、航天先驱意识到,需要在外太空极端条件下实现顺利书写,民间流传美国花费100w美元研发了一种特殊钢笔解决这个问题,那么,前苏联会怎么解决这个问题?

       这个问题才是喜感万分啊


关于bit-map的一些面试题:

     1、2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数

     解决方案:将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,如果对应位置的值是0,则将其置为1;如果是1,将其置为2;如果是2,则保持不变

     2、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中
     解决方案:我们使用625M的字符串。每一位设置为0. 将40亿个unsign int 遍历一遍。使用位图法将字符串中的对应位转化为1。 读取“再给一个数i” 查看bit[i] 是否为1,1则有存在,0则不存在。


       关于bit-map,个人感觉原理感觉与计数排序挺像的,不过与计数排序的数据有个区别:没有重复。毕竟可能有重复数据情况下,只能用int统计数字个数,位数组退化为计数排序的计数数组C[]

       就这样吧,每天消灭一点。。。


      参考网址:

1、http://www.cnblogs.com/yjf512/archive/2010/11/04/1868899.html 

 2、海量存储的牛人博客貌似域名失效,就不贴了


你可能感兴趣的:(《编程珠玑》学习总结1—bitmap)