8、大数据排序相关
12、桶排序之大数据排序
15、Hadoop-MapReduce-TeraSort-大数据排序例子
17、大数据处理算法三:分而治之/hash映射 + hash统计 + 堆/快速/归并排序
19、大数据面试题(重要)
23、大数据, 海量数据算法
一、查找
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的数即可。
题目:
如果有一个20g的日志文件,日志文件记录着用户访问过的url,每一行为一个url,给你一台512M的主机,找出出现次数最多的10个url
参考答案及思路:
1. Top K算法:使用堆排序算法+大顶堆+10个元素的数组。
2.
- IP地址最多有2^32=4G种取值情况,所以不能完全加载到内存中处理;
- 可以考虑采用“分而治之”的思想,按照IP地址的Hash(IP)%1024值,把海量IP日志分别存储到1024个小文件中。这样,每个小文件最多包含4MB个IP地址;
- 对于每一个小文件,可以构建一个IP为key,出现次数为value的Hash map,同时记录当前出现次数最多的那个IP地址;
- 可以得到1024个小文件中的出现次数最多的IP,再依据常规的排序算法得到总体上出现次数最多的IP;