这个问题是编程之美中第一章开篇提到的问题,bitmap排序。大概意思是这样的,要对n个不重复的整数进行排序,每个数小于n(10的7次方)要求内存1M。其实是区号为800开始的电话号码,800不算在内。这个是典型的位图(bitmap,应该是位映射我觉得,估计是好翻译才把map翻译成图的)。下面是代码:
#include <iostream> using namespace std; const int NUM_COUNT = 10000000; const int MAXLINE = 9; //缓冲区大小,最大读到的为8位整数 const int MASK = 0x1F; //后5位表示在一个int型中的对应的位 const int SHIFT = 5; inline void setBit(int *a, int num); inline int getBit(int *a, int num); int main() { int a[NUM_COUNT/32 + 1] = {0}; FILE *fp; char buf[MAXLINE]; fp = fopen("./num.txt", "r"); if(fp) { while(fgets(buf, MAXLINE, fp) != NULL) { int num = atoi(buf); setBit(a, num); } } for(int i=0; i<NUM_COUNT; i++) { if(getBit(a, i)) printf("%d\n", i); } return 0; } inline void setBit(int *a, int num) { a[num>>SHIFT] |= (1<<(num & MASK)); } inline int getBit(int *a, int num) { return a[num>>SHIFT] & (1<<(num & MASK)); }
编程珠玑后面还有几个习题供思考,简单写下
(1)程序要求有1M空间,但是我们的代码要使用1.25M的空间,如果1MB空间是严格的边界,如何处理?
1M内存空间共有位为1*1024*1024*8=838W,还差170w个位。可以按照数的特征去掉一部分数,例如没有以0和1开头的电话,这样就去掉200w个,就可以满足了(这个是习题解答上给的,没有以1开头的电话么)。还可以用两趟排序来解决。
(2)如果不是每个整数最多出现一次,而是每个整数最多出现10次,如何改进?
这个用四位即可表示0~15,所以用四位表示一个整数即可(可以是每个整数最多出现15次)。代码如下:
#include <iostream> using namespace std; const int NUM_COUNT = 10000000; const int MAXLINE = 9; //缓冲区大小,最大读到的为8位整数 const int MASK = 7; //后3位表示在一个int型中的对应的位 const int SHIFT = 3; //4比特表示一个数,一个32位的整数能保存8个数 inline void addBit(int *a, int num); inline int getBit(int *a, int num); int main() { int a[NUM_COUNT/8 + 1] = {0}; FILE *fp; char buf[MAXLINE]; fp = fopen("./num.txt", "r"); if(fp) { while(fgets(buf, MAXLINE, fp) != NULL) { int num = atoi(buf); addBit(a, num); } } for(int i=0; i<NUM_COUNT; i++) { if(int count = getBit(a, i)) while(count > 0) { printf("%d\n", i); count--; } } return 0; } inline void addBit(int *a, int num) { //先取出来num对应的四位的值value int value_mask = 15<<((num & MASK) * 4); int value = a[num>>SHIFT] & value_mask; value >>= ((num & MASK) * 4); //数目加一,表示多出现一次 value++; //重新设置原来的的4位值 value <<= ((num & MASK) * 4); a[num>>SHIFT] &= ~value_mask; a[num>>SHIFT] |= value; } inline int getBit(int *a, int num) { int value_mask = 15<<((num & MASK) * 4); int value = a[num>>SHIFT] & value_mask; value >>= ((num & MASK) * 4); return value; }
(3)以前免费电话的区号都是800,现在又有区号为877、888等电话,又该如何按照电话号码排序呢?
两种思路:
1)给每个区号赋予一个权值(为素数),例如800为2,877为3,888为5,用乘积来表示不同区号相同号码出现的情况,例如只出现800的某个电话,则为2,出现800和877的同一个号码,则为6,同理都出现为30。情况一共有八种,一个都没出现、只有一个出现、只有一个没出现、都出现这八种情况,可以用三位来表示,如果对内存有要求,用三位来表示这八种情况即可。这里只是一种思路。当区号较多的时候,素数难找,而且占用空间较大
2)还是位图,每个号码用三位表示即可,每一位对应一个区号。如果内存有要求,从前往后多次排序。