大数据排序、查找方法

来源:互联网 发布:js 数组比大小 编辑:程序博客网 时间:2024/05/16 00:53

                                                                      参考  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)。

(1)首先看一下10亿个正整数,正整数可以表示的范围为1到2的31次方-1。
10亿也就是1*10^9,2^31次方=2*1024*1024*1024>20亿
再想起int为32位。
再想起位图法。
位图法也就是对于出现的数,其中每1bit代表这个数,如果该位为1,则说明该数出现;如果该位为0,则说明该数没有出现。
那多大的内存能够表示10亿的数呢?
1 byte = 8 bit
1024 byte = 8*1024 bit = 1k
1024 k = 8*1024*1024 bit = 1M = 8388608 bit
将10,0000,0000处以8388608得到119.20928955078125
也就是差不多120M的内存,可以表示全10亿的数。
所以可以建立120M的一个位图,将其所有位设置为0,然后开始遍历这10亿个整数,每遍历一个,则对应到位图中相应的位置1,如果对应到位图中相应的位已经置1了,则说明这个数是要找的那个重复的数。用这种方法,最多就是遍历一遍,将这个10亿个正整数遍历完。而使用的内存为120M左右。
当然,题目中要求是小于100M。其实写到这里,似乎感觉这个题目是在哪里看到过。似乎是《编程珠玑》或者类似的书中,当然,最初的来源肯定是编程珠玑,关于电话号码的部分。
于是下一步我就是将这本书翻出来,结果就是在开篇就是关于这个问题。
不过我们遇到的问题是10亿个数,100M内存。而书中的问题是10^7个正整数,1M的可用主存。书中的问题乘以100,就正好是我们遇到的问题了。不过书中的问题是去掉所有重复的数,并将结果是一个有序的排列。
如果严格的使用100M以下内存的话,我们只能利用磁盘作为虚拟存储空间。
如果使用磁盘的话,应该就会涉及到外排序之类的。
或者是虚拟内存的管理,页面的换入换出?
其实我们这里的问题并不需要完全排序,而只是需要找出重复的数就可以。是否可以不用排序就得到?
再想想,其实题目出的有问题,应该是最大不会超过10亿,不然位图法也不行。或者就需要做hash来得到对应关系了。
(2)100M的内存=100*1024*1024*8bit=8,3886,0800 bit。我们做个取整,那么100M可以用的bit数将由8亿。
那么用每个bit来表示1个数,0表示没有出现,1表示出现,我们用取模的方式num%8亿来将数映射到8亿个bit中去,最前的2亿个bit会被重复映射,而剩下的6亿个bit只被映射一次;(step1,遍历一次)
由于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)。
2、分割法10亿个不重复的整数,查找数值大小在中间的数,要求给出效率最高的算法
/*   我的思路是这样的,假设是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

3、建堆法从1亿个整数里找出100个最大的数(用哪种算法效率高)
我的思路:
(1)读取前100个数字,建立最大值堆。
(2)依次读取余下的数,与最大值堆作比较,维持最大值堆。可以每次读取的数量为一个磁盘页面,将每个页面的数据依次进堆比较,这样节省IO时间。
(3)将堆进行排序,即可得到100个有序最大值。
二、亿级排序

输入:一个最多含有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的数即可。