《Programming Pearls》
2.2 给定包含4300000000个32位整数的顺序文件,如何找出一个出现至少两次的整数?
【方法一】
思路:考虑两个条件
1.所有的整数都存储在顺序文件中,因此,读取文件的次数将明显影响算法的效率;
2.顺序文件中包含的整数个数为4300000000,如果全部读取放在内存中的话,必须要考虑内存空间因素。
解决方案:
由上面的问题,想到了Bit-Map,可以申请537500000个char型数组,数组中每个位对应4300000000个整数中的一个数,刚开始时,将所有的位都置为0,如果有存在相对应的数,那么对应的位就置为一。
问题又出来了,如何才能表示至少包含两次的整数呢?
这时,我们发现,要表示至少包含两次的整数,仅用一位来表示是不够的。那么用两位呢?00表示没有数据,01表示存在一个,10表示存在两个,11表示存在两个以上。
这样,需要申请大小为1075000000的char类型的数组,两位对应一个数。
初始时,所有位都置0,然后开始读取顺序文件,读到整数后,相应的位做相应的改变。
这样,我们便只需要一次操作,而且使用了最少的内存便解决这个问题了。
【方法二】
BinarySearch
不是对文件内容折半,而是对搜索范围折半。由于4.3G>32位的整数空间,根据鸽笼原理一定会有重复的整数。搜索范围从所有的32位正整数开始(全部当成unsigned int,简化问题),即[0,2^32),中间值即为2^31。然后遍历文件,如果小于2^31的整数个数大于2^31,则调整搜索范围为[0,2^31],反之亦然;然后再对整个文件遍历一遍,直到得到最后的结果。这样一共会有logn次的搜索,每次过n个整数(每次都是完全遍历),总体的复杂度为O(nlogn)。
例子:数组[4,2,5,1,3,6,3,7,0,7],假定从3位的整数空间内搜索。第一次的范围为[0,8),遍历后发现[0,4)范围内的个数为5,于是调整为搜索[0,4)范围内的整数。第二次发现[2,4)范围内的整数为3,大于2,于是调整为[2,4)。再经过第三次的遍历,找出3为重复出现的整数。
改进:上面的办法有很多的冗余。于是提出了一个办法:建立一个新的文件(是顺序文件就可以)。在一次遍历过后确定搜索的范围后,把原有文件里这个范围内的整数写到新的文件里去,下次搜索就只要搜索这个文件了。这样可以近似线性的复杂度(但是常数项应该很大)。
【方法三】
若整数是排好序的,一个线性查找的解法
/**
* 问题描述:
* 给定包含4 300 000 000个32位整数的顺序文件,
* 如何找出一个至少出现两次的整数
*
* @author loo
*
*/
public class FindTwice {
/**
* 由于4 300 000 000 >2^32,所以必然存在重复的整数
* 考虑到内存的问题,可以先读取一部分,然后查找
* 这里假设一次读取10个
*/
public static void main(String[] args) {
int[] arr = {2,3,4,5,7,11,12,12,13,14,15};
int iCount=0;
int increase=arr[0]; // suppose=arr[0];
for(;iCount<arr.length;iCount++){ // suppose++;
if(arr[iCount]>iCount+increase){ // if(arr[i]>suppose)
increase+=(arr[iCount]-iCount-increase); // suppose+=arr[i];
continue;
}
if(arr[iCount]<iCount+increase){ // if(arr[i]<suppose)
System.out.println("重复的数字是:"+arr[iCount]);
break;
}
}
}
}