计算大数据的中位数
题目:在一个大文件中有10G个整数,乱序排列,要求找出中位数(内存有2G限制,不能一次全部加装),请写出算法设计思路。
中位数的定义:对于一个排序好的序列,如果数据有奇数个的话,中位数就取中间的一个;如果有偶数个的话,中位数一般取中间两个数的平均值。
解题:
思路一:堆排序(转换为求前5G大的元素)
堆排序的常用场景之一是选择topk元素,这种做法不需要对数据进行排序,只需要拿新数据与堆顶数据进行比较即可,它也不需要一次性将全部数据都加载到内存,只需要构建一个k个大小的堆。当我们需要求前k小的数据时,构建一个大顶堆,当读取的新数据比堆顶元素大时舍弃,比堆顶元素小时,替换掉堆顶元素并进行堆化,重复这一过程直至读取完全部数据,最后堆中的数据就是前k小的数据。当我们需要求前k大的数据时,构建一个小顶堆,当读取的新数据比堆顶元素小时舍弃,比堆顶元素大时,替换掉堆顶元素并进行堆化,重复这一过程直至读取完全部数据,最后堆中的数据就是前k大的数据。
对于10G的数据,它的中位数就是第5G个元素,但是内存只有2G,无法构建大小为5G的堆。因此,我们先遍历数据,构建一个1G大小的大顶堆,得到堆顶元素,即第1G大小的元素,然后利用该元素构建一个新的1G大小的堆,求出第2G大的元素,依次类推直至求出第5G大的元素。
这种方法需要多次遍历全部数据,频繁的磁盘IO操作降低了效率。
思路二:借鉴桶排序思路(推荐)
1.假设整数为有符号的32位数据,其取值范围是[-2^31, 2^31-1],将范围等份划分10000段,即thread_1[],thread_2[]...thread_10000[]。
2.将数据分为10组依次读入。首先装载第一个1G个数,遍历这些数,看它们落入thread_1[]至thread_10000[]的哪个区间,落入的对应区间统计计数增1;然后装载第二个1亿个数,重复比较与计数。
3.数据全部装载一次之后,从thread_1[]的计数开始累加,直至计数累加到全部数据的一半(假设为N/2),那么第N/2个元素所在的区间thread_i[]就包含了中位数。
4.假设中位数区间的范围是[a, b],且前面所有区间的数据计数共有m个,再次按照每组1G个元素遍历全部数据,对于处在thread_i[]区间的每个元素都进行计数。
5.当thread_i[]区间每个元素的计数累加之和加上m第一次超过N/2时,该计数处的元素就是中位数。
这种方法需要两次遍历全部数据,效率较高。