问题:
输入:一个最多包含n个正整数的文件,每个数都小于n(n=1000W)。如果在输入文件中有任何整数重复出现就是致命错误。没有其他数据与该整数相关联。
输出:按升序排列的输入整数的列表。
约束:最多有(大约)1 MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化了。
方法一:归并排序。(耗时间)
归并排序需把数据全部读入内存,1000w个整数的大小是1000w*4/(1024*1024)大约是40M(假设一个整数是4字节),显然不能将全部数据读入内存。
方法二:分段排序。(耗时间)
将这些整数分成40组,分别是[0-249999] [250000-4999999] … [9750000-9999999],然后遍历40次。这样时间复杂度是40n,空间复杂度是n/40,比归并要好点。当然也可以把范围扩大或者缩小。
方法三:位图法。
用1位来表示[0~n-1]中的整数是否存在。1表示存在,0表示不存在。这样的话进行一次遍历,就可以进行排序了,而且空间复杂度是n/(8*1024*1024)。例如对于20个数来说,如果集合是{1,2,3,5,8,13}.那么其位图表示法如下:01110100100001000000(说明:左边第一位表示 0 )。
程序:
利用bitset
#include <iostream>
#include <bitset>
#include <fstream>
using namespace std;
int main()
{
}
利用位逻辑运算
事实上,我们是用每一个元素表示一个32位的二进制字符串这样这个元素可以保留相邻32个号码是否存在的信息,数组范围就下降到了10000000/32了,例如对于号码89256,由于89256 mod 32=2789…8,这样我们应该 置a[2789]中32位字符串的第8位(从低位数起)为1现在问题的关键是,如何用位逻辑运算来表示这种操作。
#include <stdio.h>
#include <stdlib.h>
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/32];
// 置位函数----用“|”操作符,I & MASK相当于mod操作
//m mod n 运算,当n = 2 的X次幂的时候,m mod n = m & (n-1)
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 = 0;
while (scanf("%d", &i))
set(i);
for (i = 0; i < N; i++)
if (test(i))
printf("%d\n", i);
return 0;
}