咋一看,這是個很簡單的問題,但是如果n是個不確定的數呢?比如服務器每天會收到數以億計的請求,但是目前服務器端不希望保存所有的請求,只想隨機保存這些請求中的m個。試設計一種算法,能夠使服務器實時保存m個請求,並使這些請求是從所有請求中的大致等概率被選中的結果。注意:不到一天的結束,是不能提前知道當天所有請求數n是多少的。下面我們分兩種情況討論(1)n已知,(2)n未知。
1 n已知
可以將問題簡化為:從集合A(a_1, a_2, … ,a_n),中隨機選取m(0≤m≤n)個元素,使得每個數被選取的概率相等。可以很簡單的計算每個數被選取的概率是m/n。如果集合A里面的元素本來就具有隨機性, 每個元素在各個位置上出現的概率相等, 並且只在A上選取一次數據,那么直接返回A的前面m個元素就可以了, 或者可以采取每隔k個元素取一個等類似的方法。這樣的算法局限很大, 對集合A的要求很高。
假設集合A中的元素在各個位置上不具有隨機性, 比如已經按某種方式排序了,那么我們可以遍歷集合A中的每一個元素a_i,根據一定的概率選取a_i,這個概率是多少呢,設m’為還需要從A中選取的元素個數, n’為元素a_i及其右邊的元素個數, 也即n’=(n-i+1)。那么選取元素a_i的概率為 m’/n’。這個證明較復雜,下面簡單驗證一下前兩個元素被選中的概率:(設p(a_i=1)表示a_i被選中的概率,p(a_i=0)表示a_i沒有被選中的概率)
(1)很顯然 p(a_1=1)=m/n
(2)p(a_2=1)= p(a_2=1,a_1=1)+p(a_2=1,a_1=0)
= p(a_1=1)*p(a_2=1│a_1=1)+ p(a_1=0)* p(a_2=1│a_1=0)
= m/n * (m-1)/(n-1) + (n-m)/n*m/(n-1)
= m/n
實際編程中選取某個元素時,可以生成一個[0,1]之間的隨機數k, 若k<=m'/n'則選取這個元素,否則拋棄。
2 n未知
這個問題可以簡化為:一個整數序列生成器,以一定時間間隔生成一個新的整數,一天之內會生成N個,希望實時保存m個整數,使得任何時刻這m個整數都是當前已生成的所有整數數量n中等概率抽取的結果,即概率均為m/n。由於n是未知的,我們需要以某種特殊的方式進行判決保存還是不保存,以使得滿足概率要求。具體步驟如下:
(1)對於前m個請求直接保存到服務器上,對應整數序列相當於,整數數組的前m個直接存下來。
(2)對於m個以后的第k個新請求,以m/k的概率選擇保存,並同從已保存的m個請求中隨機選出的一個進行交換。
細說就是,
- 對於第m+1個請求,以m/(m+1)的概率選擇留下,如果留下了則從已保存的m個請求中隨機選出一個,同它交換;
- 對於第m+2個請求,以m/(m+2)的概率選擇留下,如果留下了則從已保存的m個請求中隨機選出一個,同它交換;
- 對於第m+3個請求,以m/(m+3)的概率選擇留下,如果留下了則從已保存的m個請求中隨機選出一個,同它交換;
…
下面我們用數學歸納法證明這個方法使每個元素被選取的概率是m/n:
(1)當n=m+1時,
對於第m+1個請求以概率m/(m+1)選擇留下,顯然滿足m/n的要求;
對於前m個請求中的任何一個,能被選擇留下有兩種情況:a.第m+1個請求被選擇留下了並且沒有和自己進行交換; b.第m+1個請求沒有被選擇留下來而自己確實已被選擇留下來了。
所以,概率計算為 m/(m+1) * (m-1)/m + (1 – m/(m+1)) * 1 = (m-1)/(m+1) + 1/(m+1) = m/(m+1)
(2)假設當n=N時,仍然正確,即任何一個請求被選中的概率都是m/N,現在推到證明當n=N+1時,任何一個請求被選中留下的概率是m/(N+1)。
對於第N+1個請求,因為是以m/n=m/(N+1)的概率選中的,所以顯然滿足要求;
對於前m個請求中任何一個,能被選中留下同樣分為同上的兩種情況:a.一種是第N+1個被選中了但隨機抽取出與它交換的不是自己; b.另一種情況是自己已留下並且第N+1個未被選中留下。並且前m個請求中的某個被選中的前提是:在處理完第N個請求后,該請求被選中,根據假設這個概率是m/N。
和概率為: [ m/(N+1) * (m-1)/m + (1-m/(N+1)) ]* m/N = [ (m-1)/(N+1) + (N+1-m)/(N+1) ] * m/N = m/(N+1)。
即當n=N+1時,仍然正確。
綜合1)、2)可知,此方法滿足等概率要求
當然這種選取方法也適用於n已知的情況。
【版權聲明】轉載請注明出處 http://www.cnblogs.com/TenosDoIt/p/3364139.html