中位数并不是大小位于中间的数,而是排序之后,位置位于中间的数。
若是n个数,n为奇数,则中位数是数组a[ ]排序之后 a[(n+1)/2] ; 若n为偶数,中位数是(a[n/2] + a[n/2+1])/2
如: 5 5 5 6 7 8 9 中位数是6
题目:
只有2G内存的pc机,在一个存有10G个整数的文件,从中找到中位数,写一个算法
对于海量数据求中位数,在网上看到两种方法。
1)分区间堆排序:
在现有M大小内存情况下若最多能够造出包含p个数据的堆,则先扫描一次这N个数据找到最小的p个数,耗时O(Nlog(p)),设这p个数中最大的数是a,将堆清空,在第二轮扫描出比a大的中最小的p个数,然后在把a改为记录这p个数中最大的数,依次类推,直到计算到某一轮p个数和之前够造出的数的个数大于N/2,在这p个数中找到所有数中的中位数,耗时的地方是每轮扫描构建堆都要用了O(Nlog(p)),构造的次数为N/(2*p),所以它的时间复杂度是O(N*N*log(p)/(2*p)).
2)基于段的计数
基于段的计数指的的是对一个区间范围的计数,与计数排序不同的是后者对每一个数出现次数的计数,而前者是对出现在某一区间范围内的数计数,段的大小取决于内存大小和每段计数器所占的字节数,而计数器大小又受总数据量有关,需满足计数器的最大计数能够大于最大个数N,
比如本题中内存大小是M个字节,而N的大小至少需要用logN位来表示,设k个字节表示的无符号整数可以最大值大于N,则放在内存中计数器个数是M/k,设q=M/k,即这N个数分为q个段,每一段的大小是n=N/q,第一段数的范围是0~n-1,第二段是n~2*n-1,依此类推。现在我们可以从硬盘中逐个扫面这N个数,根据每个数的大小来修改他们对应范围的计数器,需要用O(N)时间完成对这q个段的计数。然后从第一段开始往后扫描累加每个段的计数器,直到累加到某一段的计数器使得它和这之前的个数大于N/2,假设这个段所表示的范围是q~q+n-1,那么这N个数的中位数就在q~q+n-1这个范围内,现在更改计数器的属性不在基于段而是基于每个数,且只针对q~q+n-1这n个数计数,还需要一轮扫面N个数来完成对n个计数器的确定,在本段之前的段计数器计数之和是t,t<N/2,那么只要依次从新的n个计数器开始累加到s,使得满足t+s>N/2那一个计数器,它所代表的数就是这N个数的中位数。