这个问题是编程之美中第一章开篇提到的问题,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)还是位图,每个号码用三位表示即可,每一位对应一个区号。如果内存有要求,从前往后多次排序。