编程珠玑第一章,电话号码排序,问题是这样的,给你大约1000万个数字,让你用最快的方式进行排序,限制如下:
1. 内存大约1M
2. 数字都是整数,且不重复
3. 数字的范围最大是7位数。
文章提到了大约三种解法,
第一种是归并算法,只不过不在内存,在外存上做归并排序。大致思路是将原始文件读入,然后按照数字的大小划分10份,写到10个文件里。
这样每个文件就小于1M,然后对每个文件进行排序,排序之后,将文件合并。由于file1<file2<...<file10。所以合并很简单,直接依次读一遍,再写文件即可。
读次数:1+10+10
写次数: 10+10+1
第二种二种比较傻,分多次读,每次只读i~ j的数字,然后对其排序,并输入到文件。这个方法不再赘述。
第三种就是使用bitmap,思路就是把一个32位的整数用来存32个数字,比如,如果数字i出现了,则该数的二进制的第i位为1,否则为0。这样1000万个数字大约要1.25M.
首先读入所有的数字,存入bitmap,然后遍历bitmap,如果第i位为1,表示出现过,则输出到文件,否则,不输出。
下面是标准答案,感觉位运算写的很好,很高效,学习下:- #include <iostream>
- #include <cstdio>
- using namespace std;
- #define BITSPERWORD 32
- #define SHIFT 5
- #define MASK 0x1F
- #define N 1000000
- int a[1+N/BITSPERWORD];
- /*
- * 参考代码写的bitmap实在是太巧妙了,全部都是移位和位运算,非常高效。
- * 常用技巧:
- * 1. 求n除以2^i的余数
- * 最低效:n%(2^i)
- * 低效:n-(n/2^i)*2^i
- * 高效:n-(n>>i)<<i
- * 最高效: 从上面的过程可以看出,求除以2^i后的余数即保留低i位的二进制,其余位变0
- * #define MASK 0x1F (假设是i=5)
- * n&MASK
- *
- * 2. 将n的第i个二进制位置1或0
- * 置1:n=n|(1<<i)
- * 置0: n=n&~(1<<i)
- */
- 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)); }
- void reset() { memset(a,0,sizeof(a)); }
- int main(){
- int i;
- int n,m;
- reset();
- scanf("%d",&n);
- for(i=0;i<n;i++){
- scanf("%d",&m);
- set(m);
- }
- for(i=0;i<100;i++)
- if(test(i))
- printf("%d\n",i);
- return 0;
- }