参考 http://bbs.chinaunix.net/thread-1264289-10-1.html
http://yangyou230.iteye.com/blog/1315439
http://blog.csdn.net/guyulongcs/article/details/7520467
一、查找
1、位图法10亿个正整数,只有其中1个数重复出现过,要在O(n)的时间里面找出这个数,内存要尽可能少(小于100M)。
由于10亿个正整数,只有其中1个数重复出现过,因此如果在映射到这后6亿个bit中,若发现某bit位已经是1,那么我们就提前找到这个数了;
否则我们可以认为重复的数是那些被映射到前2亿位的数。因此只要在第一遍映射中没有发现重复的数,则接下来我们只需要用4亿个bit来判断重复的数,即,前2亿个bit用来记录num/2亿=0的数,而后2亿个位用来记录num/2亿=4的数,这样同样若发现某bit位已经是1,那么我们就提前找到这个数了。(step2,遍历一次)。一共遍历了2遍,时间复杂度O(2n)=O(n)。
/* 我的思路是这样的,假设是32位的无符号整数,共有2^32 = 2^16 * 2^16 个整数,把全部整数等分成2^16个区间 然后统计每个区间里面的数的个数,确定中位数是在哪个区间里面,再对10亿个数中落在该区间里面每个数上的个数进行计数 这样进行从小进行加和就可以确定中位数了 */ //这里没有考虑10亿个数是怎么存储,怎么读的,只是假设他存在数组a[N]中, N在这里等于10亿 int SelectMedian() { int zone[2^16] = { 0 }; int MediumZone[2^16] = { 0 }; int i = 0; for(; i< N ; i++) zone[a/(2^16)]++; int sum = 0; i = 0; do sum += zone[i++]; while(sum < N/2) sum -= zone[i-1]; izone = i-1; //这里,我们把中位数所在区间叫中位组,存储中位组的标号 int floor = 2^16 * izone, ceiling = 2^16 * (izone + 1); //floor 和ceiling 用来存储 中位组的下限和上限 for(i=0; i<N; i++) if(a >= floor && a <= ceiling) MediumZone[a-floor]++; i=0; do sum += MediumZone[i++]; while(sum < N/2) return a[floor + i -1]; } //这里遍历了10亿数两次,用了两个2^16 = 16k大小的数组, 时间复杂度 为o(n), 空间用了32k //这是对32位数而言的,对于64位数 2^64 = 2^16 * 2^16 * 2^16 * 2^16 分四次就可以了, 算法复杂度依然是o(n), 空间用 16k * 4 = 64k |
输入:一个最多含有n个不重复的正整数(也就是说可能含有少于n个不重复正整数)的文件,其中每个数都小于等于n,且n=10^7。
输出:得到按从小到大升序排列的包含所有输入的整数的列表。
《编程珠玑》中提出的问题,有三种解法:
(1)磁盘合并排序
先将所有数据分成多个小文件,多个小文件采用内部排序后,再用多路合并排序完成排序输出。
总数据为n, 内存中采用内部排序最多m。先分成n/m个小文件,再内部排序,第三部读取所有小文件,每次将最小的数输出即可。
(2)多通道
0~10^k-1
10^k~2*10^k-1
...
分成m个通道,读m次,每次读取在通道范围内的数,按顺序写到对应的输出文件,完成排序。
(3)bitmap排序
在内存中开10^7比特,均初始化为0,若出现则设置为1,输出为1的数即可。