海量数据取中位数
轉自:http://hi.baidu.com/mxp446533129/blog/item/a6025efa293f1c00d9f9fdaf.html
若有很大一組數據,數據的個數是N(每個數占4個字節),內存大小為M個字節,其中M<4*N,使得不能在現有內存情況下通過直接排序找到這N個數的中位數。解決海量數據中取中位數的方法有兩種比較簡單耗時的是用堆排序,還有一種是改造后基于段的計數:
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<2/N,那么只要依次從新的n個計數器開始累加到s,使得滿足t+s>N/2那一個計數器,它所代表的數就是這N個數的中位數。 與50位技術專家面對面20年技術見證,附贈技術全景圖
總結